Can't get hasManyThrough to work - laravel

I've got three tables:
nCompanies (model Company)
nTransactions (model Transaction)
nBrokers (model Broker)
One company may have many transactions
One broker may have many transactions
Company and Broker doesn't have a relation.
Now I would like to get a list of brokers for each company through the company transactions. In order to do this I thought hasManyThrough() would do the job. But I can't get it to work.
Within my company model I have this:
class Company extends Model
{
protected $table = 'nCompanies';
protected $primaryKey = 'nCompanyId';
public function transactions()
{
return $this->hasMany('App\Transaction', 'nCompanyId');
}
public function brokers()
{
//This does not work
return $this->hasManyThrough('App\Broker', 'App\Transaction', 'nCompanyId', 'nBrokerId');
}
}
The below works, but I would prefer to do this with in the model in order to get a collection of brokers for each company, company->brokers instead of a flat list.
$companies = DB::table('nCompanies')
->join('nTransactions', 'nCompanies.nCompanyId', '=', 'nTransactions.nCompanyId')
->join('nBrokers', 'nTransactions.nBrokerId', '=', 'nBrokers.nBrokerId')
->selectRaw('nBrokers.*, nCompanies.*)
->groupBy('nCompanies.nCompanyId', 'nBrokers.nBrokerId')
->get();
How could I solve this? Should I use something else instead of hasManyThrough?

I re-read your question, and realized you wanted to get the brokers and companies through transaction. I guess you already have both nCompanyId and nBrokerId in transactions table. Then I guess the brokers should be accessed in Transaction model then so you can have:
class Transaction extends Model
{
public function brokers()
{
return $this->hasMany('App\Broker', 'nBrokerId');
}
}
So you can call it with:
$brokers = Company::find(1)->transactions->where('id', '1')->brokers();
This is just an example of my thoughts about this though:
I don't know if this helps you get what you are trying to do. :)

HasManyThrough does not work here because the relationship between Transaction and Broker is wrong: While there is a HasMany relationship Company->Transaction, there only is a HasOne relationship Transaction->Broker. For HasManyThrough to work, both have to be HasMany though.
A proposed solution would be to use an accessor together with a subquery in your model, like this:
public function getBrokersAttribute()
{
$transactionIds = $this->transactions->groupBy('nBrokerId')->lists('nBrokerId');
return Broker::find($transactionIds);
}
Be aware that this would cause two seperate queries, even though Laravel would cache the result of $transactionIds after first load. Also, this solution does not support eager loading and is not recommended when working with many companies.
Alternatively, you could use some looping to keep the ability of eager loading:
public function getBrokersAttribute()
{
$brokers = new Collection();
foreach ($this->transactions as $transaction) {
if (!$brokers->contains($transaction->broker->nBrokerId)) {
$brokers->add($transaction->broker);
}
}
return $brokers;
}
With this example, you can eager load using ->with('transactions.broker').

Related

Laravel Polymorphic get all models associated to a single model

I have an Attachment model which uses the morphs table and morphTo function. I have several models but the main ones include Client, Job, Project and Notes (which is also polymorphic) which can contain several Attachments.
A client is the top-level model. A Client has many Jobs. A Job has many Projects.
I am struggling with a single way to return all attachments of a client, including attachments of each Job, Project, and notes of each job/project under a client.
I currently am running several foreach loops and have a working way, but the queries on the page load range from 60-100 depending on the amount of jobs/projects/notes for each client. I run through each job to check if it has an attachment, if so, I loop through them. Then, I run through $job->notes->attachments and display those. From there, I dive into another foreach loop pulling all the job's projects, pulling the attachments from each project and then pulling all the notes and looping through that.
Is there a way within Laravel to get all of the Attachments that are somehow attached to a single Client without looping through the way I have? If not, is there a way I can optimize my loops so I don't have to request the attachments for each job/job's notes/project/project's notes?
I do this all the time. You just need a way to
"...get all of the Attachments that are somehow attached to a single
Client without looping through..."
You must consider custom joins, using Laravel Eloquent:
//client_id input here
$client_id = 10;
$att_from_client = Attachment::join('note', function ($join) {
$join->on('note.id', '=', 'attachment.object_id')
->where('attachment.object_type', 'App\\Note');
})
->join('project', 'project.id', '=', 'note.project_id')
->join('job', 'job.id', '=', 'project.job_id')
->join('client', 'client.id', '=', 'job.client_id')
->where('client.id', $client_id)
->get();
dd($att_from_client);
My advice is to use eloquent-has-many-deep. As example of you can do with that library you can look at the code of three models related with many to many:
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\Models\User')->withTimestamps();
}
public function permissions()
{
return $this->belongsToMany('App\Models\Permission')->withTimestamps();
}
}
class Permission extends Model
{
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function roles()
{
return $this->belongsToMany('App\Models\Role')->withTimestamps();
}
public function users()
{
return $this->hasManyDeep('App\Models\User', ['permission_role', 'App\Models\Role', 'role_user']);
}
}
class User extends Authenticatable
{
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function roles()
{
return $this->belongsToMany('App\Models\Role')->withTimestamps();
}
public function permissions()
{
return $this->hasManyDeep('App\Models\Permission', ['role_user', 'App\Models\Role', 'permission_role']);
}
}
With these relationships in place and 5 tables involved: users, role_user, roles, permission_role and permissions you can retrieve all the permissions of a User model with a call to $user->permissions, that resolves to only one query with all the joins needed.

Laravel Eloquent Relation belongsTo update

I am trying to update/delete/create in belongsTo relations.
Company has many sports
sports is belonging to Company
Here is two models.
class CompanySports
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
class Company
public function sports()
{
return $this->hasMany(CompanySports::class,"company_id","id");
}
}
at controller, when sports is added or modified or remove, what is the best practice to update?
i know that many to many, sync can be used. In this, what is the best solution? Should i compare everytime after loading all from database which is not good practice i believe.
From your code, I would first recommend putting your models in separate files, and ensuring they are singular. If you use the artisan make:model command to generate the stubs, it should do this for you.
// app/CompanySport.php // <-- NOTE singular
class CompanySport // <-- NOTE singular
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
}
// app/Company.php
class Company {
public function sports()
{
return $this->hasMany(CompanySport::class,"company_id","id"); // singular
}
}
From there, I find it helpful to build helper methods in the various classes so that the grammar sounds natural and more importantly, belongs to the model. For example:
// app/Company.php
class Company
{
...
public function addSport(CompanySport $sport)
{
$this->sports()->save($sport);
}
public function removeSport(CompanySport $sport)
{
$this->sports()->find($sport->id)->delete();
}
}
These helper functions can then be easily called from anywhere, e.g. controller:
// CompanySportsController.php
public function store(Company $company, CompanySport $sport)
{
$company->addSport($sport);
return redirect('/company/' . $company->id);
}
If you are using these helpers, there is no comparing or sync to be done since you are only using a one to many relationship. Eloquent does everything for you.
Also, I've found this cheatsheet particularly helpful when building out the initial relationships and scaffolding of a new app.
While adding new record of Company Model, you need not to do anything as there is no child for it yet.
While updating an instance of a Company model, again you need not to update anything on its children. As relationship are based on id(primary key) which I believe you don't change while updating.
And now for deleting there are some questions. Do you want to delete the children when the parent is deleting? If so, you can use ON DELETE CASCADE which you can set up in migration like
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
in your spors table.
Well you can make your own function too like answered in here
Well if you don't want to delete the children, you can use softdelete on your Model. set up the relations then like
CompanySports
public function company()
{
return $this->belongsTo(Company::class, "company_id","id")->withTrashed();
}
This way you can get the parent of a children without any error though the parent is deleted.

Laravel hasMany relationship detach all

Is there a simple solution to detach (without deleting) all related models through a hasMany Relation on a model?
For example, I have two tables:
College (id, name)
Student (id, name, college_id(nullable) )
In the College model I define this relationship:
class College extends Model
{
public function students()
{
return $this->hasMany('App\Student','college_id','id');
}
}
What is the best way to detach all current College students from the College (i.e, get all the students of the college and set their college_id to null)?
Is there an easy way to detach all students from the College model using eloquent?
Something like
class College extends Model
{
...
public function detachAllStudents()
{
...
}
}
P.S. already have read this question Laravel hasMany detach, but get errors when I try to apply it to my application
Yes, you can detach all the college students as below. Actually, we already have the college_id in your students table and we have to make this college_id null somehow. This is not called detach in Eloquent. The word detach come when you have many to many relationships. So, let's just update the students and see if it works or not.
$college->students()->update(['college_id' => null);
So, you method can be completed as below:
public function detachAllStudents()
{
$college->students()->update(['college_id' => null]);
}
That's it!
Directly from the documentation https://laravel.com/docs/5.8/eloquent-relationships:
Toggling Associations
The many-to-many relationship also provides a toggle method which "toggles" the attachment status of the given IDs. If the given ID is currently attached, it will be detached. Likewise, if it is currently detached, it will be attached:
$user->roles()->toggle([1, 2, 3]);
Despite Imran answer perfectly fits this scenario I would add a more generic approach. Let's say $college respects an interface instead of an implementation, you wouldn't know the foreign key at run time.
<?php
use Illuminate\Database\Eloquent\Relations\HasMany;
interface HasStudents {
public function students(): HasMany;
}
class College extends Model implements HasStudents
{
public function students(): HasMany;
{
return $this->hasMany(Student::class, 'college_id', 'id');
}
}
function detachStudents(HasStudents $model): void {
$studentsRelationship = $model->students();
$studentsRelationship->update([
$studentsRelationship->getQualifiedForeignKeyName() => null
]);
}
detachStudents($college);

How do i access data using two BelongsTo?

I have three tables - "courses", "lessons" and "tasks". Each lesson belongsTo a course, and each task BelongsTo a lesson. I want to output a task, showing the task name, the lesson name, and the course name. How do I access the course table data? To get the lesson information linked to a course, I have used the following in my Task model:
$lessonName = $this->lessons->lesson_name;
To get the course name associated to that lesson, I have tried the following with no success, but I am really guessing here:
$courseName = $this->lessons->courses->course_name;
My model relationships are as follows:
Course.php
public function lessons()
{
return $this->hasMany('App\Lesson');
}
Lesson.php
public function tasks()
{
return $this->belongsTo('App\Task', 'task_id', 'id');
}
Task.php
public function lessons()
{
return $this->belongsTo('App\Lesson', 'lesson_id', 'id');
}
Where am I going wrong? Thanks
there is another way you can do this by using accessors.
on your Task model do the following:
public function getLessonAttribute(){
return Lesson::where('id', $this->attributes[*foreign_key_field*])->first();
}
Here you receive all the data regarding the lesson that the task belongs to, and can use them as any other attribute (field) of the model.
on your Lesson model get the course that it belongs to.
public function getCourseAttribute(){
return Course::where('id', $this->attributes[*course_foreign_key_field*])->first();
}
and then assuming that $task is your collection, you can access the lesson and the course like the following in blade:
$task->lesson->lesson_name and $task->lesson->course->course_name
In your lesson.php model doesn't exist relationship courses so there are your issue. Use answer what is told you #jeroenF
So you want the inverse of hasManyThrough?
The hasManyThrough feature of Laravel (see their site) facilitates connecting your Courses to Task directly, without having the intermediate connection in a separate relationship.
You are looking for the inverse?

Filtering eager-loaded data in Laravel 4

I have the following setup:
Clubs offer Activities, which are of a particular Type, so 3 models with relationships:
Club:
function activities()
{
return $this->hasMany('Activity');
}
Activity:
function club()
{
return $this->belongsTo('Club');
}
function activityType()
{
return $this->hasMany('ActivityType');
}
ActivityType:
function activities()
{
return $this->belongsToMany('Activity');
}
So for example Club Foo might have a single Activity called 'Triathlon' and that Activity has ActivityTypes 'Swimming', 'Running', and 'Cycling'.
This is all fair enough but I need to show a list of ActivityTypes on the Club page - basically just a list. So I need to get the ActivityTypes of all the related Activities.
I can do that like so from a controller method that receives an instance of the Club model:
$data = $this->club->with(array('activities', 'activities.activityTypes'))->find($club->id)
That gets me an object with all the related Activities along with the ActivityTypes related to them. Also fair enough. But I need to apply some more filtering. An Activity might not be in the right status (it could be in the DB as a draft entry or expired), so I need to be able to only get the ActivityTypes of the Activities that are live and in the future.
At this point I'm lost... does anybody have any suggestions for handling this use case?
Thanks
To filter, you can use where() as in the fluent DB queries:
$data = Club::with(array('activities' => function($query)
{
$query->where('activity_start', '>', DB::raw('current_time'));
}))->activityType()->get();
The example which served as inspiration for this is in the laravel docs, check the end of this section: http://laravel.com/docs/eloquent#eager-loading
(the code's not tested, and I've taken some liberties with the property names! :) )
I think if you first constraint your relationship of activities, the activity types related to them will be automatically constrained as well.
So what I would do is
function activities()
{
return $this->belongsToMany('Activity')->where('status', '=', 'active');
}
and then your
$data = $this->club->with(array('activities', 'activities.activityTypes'))->find($club->id)`
query will be working as you would expect.

Resources