Eloquent ORM - access to eager-loaded columns - php

I think I'm running myself in circles here, so I'm hoping to get some help - I should say that I have very little experience with PHP/laravel/eloquent, this is an inherited project.
I have a many-to-many relationship between two classes [Ticket and Comment]. I then have a many-to-one relationship between [Comment and User]. Authors on a Ticket are defined as the distinct set of users who have comments on the ticket.
I would like to eager load author IDs when retrieving tickets (I don't need the whole user, just the id)
Originally authors was given by $this->hasMany(Comments::class)->select('author_id)->distinct()
Which is fine when only retrieving them for a single ticket but will be slow when doing it for all tickets. Attempting to eager load this, even with a foreign key will fail because the ORM can't convert it to a valid SQL statement (I don't think?)
So I though I would define authors based on the comments for a ticket and eager load the comments, then theoretically I can get the distinct authors without having to go back to the database every time:
function comments()
{
return $this->hasMany(Comment::class);
}
function authors()
{
return ($this->comments == null
? $this->hasMany(Comment::class)->select('author_id')->distinct()
: $this->comments->select('author_id')->distinct());
}
I figured then that if I call:
$tickets = Tickets::with('comments')->get();
Then the authors can be determined in memory, but I think lacking a fundamental understanding of what is going on behind the scenes is causing me issues here, as this doesn't work. I suspect I'm approaching this in completely the wrong manner and would appreciate if anyone could point me in the right direction!

Related

Get a collection of relationship items through multiple relations in Laravel

I have the following relationships:
Users have polymorphic many to many (morphedByMany) relationships with Customers, Locations, and Vendors.
Customers, Locations, and Vendors each have hasMany or hasManyThrough relationships with Datasets.
I'd like to get the Datasets that a given User has access to via its relationships. Also, some of the datasets through the different relationships might be the same, so I want a unique list.
I created the following method on my User model that works correctly (inspired by Laravel get a collection of relationship items):
public function accessibleDatasets()
{
$datasetsByCustomers = $this->customers()->with('datasets')->get()->pluck('datasets')->flatten();
$datasetsByLocations = $this->locations()->with('datasets')->get()->pluck('datasets')->flatten();
$datasetsByVendors = $this->vendors()->with('datasets')->get()->pluck('datasets')->flatten();
$datasets = $datasetsByCustomers;
$datasets = $datasets->merge($datasetsByLocations);
$datasets = $datasets->merge($datasetsByVendors);
return $datasets->unique();
}
Is there a "right way" or more efficient way to get this (besides using some sort of a reduce function for the merges)? Is loading the models, then flattening better? The associated values aren't going to change too often, so I can cache the results, but wanted to get some feedback.
The most efficient way would be by performing a single database query as Tim already mentioned.
Since you can cache the results and should you want to spare some lines of code I'd rather do:
public function accessibleDatasets()
{
$this->load('customers.datasets', 'locations.datasets', 'vendors.datasets');
return $this->customers->flatMap->datasets
->merge($this->locations->flatMap->datasets)
->merge($this->vendors->flatMap->datasets);
}
The merging operation should take care of duplicate keys but feel free to add a unique call if it doesn't.

laravel Eloquent join and Object-relationship mapping

Ok so i'm kind of newish to eloquent and laravel (not frameworks tho) but i hit a wall here.
I need to perform some queries with conditions on different tables, so the eager load (::with()) is useless as it creates multiples queries.
Fine, let use the join. But in that case, it seems that Laravel/Eloquent just drops the concept of Object-relationship and just return a flat row.
By exemple:
if i set something like
$allInvoicesQuery = Invoice::join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();
and then looping such as
foreach ($allInvoicesQuery as $oneInvoice) {
... working with fields
}
There is no more concept of $oneInvoice->invoiceFieldName and $oneInvoice->contact->contactFieldName
I have to get the contacts fields directly by $oneInvoice->contactFieldName
On top of that the same named columns will be overwrited (such as id or created_at).
So my questions are:
Am i right assuming there is no solution to this and i must define manually the field in a select to avoid the same name overwritting like
Invoice::select('invoices.created_at as invoice.create, contacts.created_at as contact_create)
In case of multiple joins, it makes the all query building process long and complex. But mainly, it just ruins all the Model relationship work that a framework should brings no?
Is there any more Model relationship oriented solution to work with laravel or within the Eloquent ORM?
Instead of performing this join, you can use Eloquent's relationships in order to achieve this.
In your Invoice model it would be:
public function contact(){
return $this->belongsTo('\App\Contact');
}
And then of course inside of your Contact model:
public function invoices(){
return $this->hasMany('\App\Invoice');
}
If you want to make sure all queries always have these active, then you'd want the following in your models:
protected $with = ['Invoice']
protected $with = ['Contact'];
Finally, with our relationships well defined, we can do the following:
$invoices = Invoice::all();
And then you can do:
foreach($invoices as $invoice)[
$invoice->contact->name;
$invoice->contact->phone;
//etc
}
Which is what I believe you are looking for.
Furthermore, you can find all this and much more in The Eloquent ORM Guide on Laravel's site.
Maybe a bit old, but I've been in the same situation before.
At least in Laravel 5.2 (and up, presumably), the Eloquent relationships that you have defined should still exist. The objects that are returned should be Invoice objects in your case, you could check by dd($allInvoiceQuery); and see what the objects are in the collection. If they are Invoice objects (and you haven't done ->toArray() or something), you can treat them as such.
To force only having the properties in those objects that are related to the Invoice object you can select them with a wildcard: $allInvoicesQuery = Invoice::select('invoices.*')->join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();, assuming your corresponding table is called invoices.
Hope this helps.

Count two rows with scopes and relationships (Laravel)

So I have two tables, one called members and one called memberships
My goal is to count the number of members who have a certain membership. I've set up foreign keys and relationships are working fine, to the point where I need to do the counts.
My scope is (in Member model)
public function scopeActive($query) {
return $query->where('membership_ended_at', Null);
}
My relationship is (in Member model)
public function membership() {
return $this->belongsTo('App\Membership');
}
This query works fine, and I see how many members who are active() that has the membership_id of 6.
$members_student = Membership::find(6)->members()->active()->count();
I don't know if that's supposed to work, but it does. Now, the issue I have is that we have a regular student membership, and a student abroad membership with the ID of 14.
I assumed maybe this would work, but I quickly realized I was wrong
$members_student = Membership::find([6,14])->members()->active()->count();
I know I can call two queries and just add the two together, but I'm looking for a more elegant solution. Something that only required one query, and will half my queries.
Hopefully someone else has seen this before
Thanks to someone on laravel.io chat I managed to figure this one out. Posting it here in case anyone else is also looking for the answer.
The solution:
$members_student = Member::whereIn('membership_id', [6,14])->active()->count();

laravel one to many(select all rows from second table)

i want to select all services rows from servs table
_____ i have two tables users with model(User) ..... and servs with model(servs) ... . uwant to select all rows from servs when it auth User
How can i do that ???
public function postserv(){
$serv = User::find(Auth::user()->id)->servs;
$serv = $serv->first();
return $serv->serv_id;
}
I'm not sure of the model name, but it should be something like Serv::all()
Your question is very vague and it's hard to determine what's going on in your project but I'll give it a shot.
If you want to select all rows of a model use the following:
Services::all()
Whilst that is what you're explicitly asking for, your question seems to pertain to a relationship where you select all services for a user.
User::find(Auth::user()->id)->servs()->get();
This will return all services that are joined to the authorised user, on the note of naming conventions you should make your relations more readable.
Also note that you must have your relationships set up in your Eloquent models else the above code would fail.
In future try to add a little more detail for your questions, there is more information about relationships in the Eloquent ORM on the Laravel website.
ModelName::all();
Returns all rows from a model/table.

Custom sorting/filtering in Doctrine 2 associations

I often sort associated entities by a foreign attribute and I'm wondering how to best handle it and still be able to use collections of the main entity. Let's have this example:
Author { $name, Comment[] $comments}
Comment { $name, Category $category}
Category { $name, $position }
I want $author->getComments() sorted by the comment's category position. In terms of DQL:
SELECT com.* FROM comment com JOIN com.category cat ORDER BY cat.position
Truth be told, my sorting criteria are rather more complex, this is just to get us started.
I know about #OrderBy annotation for *ToMany associations but it won't help me because ordering by a joined table's attribute is not supported.
I also know I can use DQL to fetch the comments but I need to access them in many places and I'd prefer referencing $author->getComments() rather than calling $commentRepository->findByAuthorSorted($author). I don't want having to remember to call my custom method to fetch the comments in the right way. I want it to be automatic.
I was considering somehow passing commentRepository to Author entity and use it in getComments() but I didn't figure out how to do it, plus it doesn't feel right to begin with.
I also thought about doing the sorting in PHP in getComments(). I don't think I can use Criteria + $author->comments->matching() because joined attributes don't seem to be supported. I'm fine with casting the collection to a read-only array but I hope there's a better, more Doctrine-way solution.
I don't want to work around the problem by adding $categoryPosition to each Comment.
I'd like to hear how you people handle this problem. I bet I'm not alone :-)

Categories