CakePHP 3 - Assocation BelongsToMany and hasMany - limit columns by select - php

Good day!
I have small problem with CakePHP 3. I have association like that:
$this->belongsToMany('AdminUserGroup',[
'classname' => 'AdminUserGroup',
'foreignKey' => 'admin_user_id',
'targetForeignKey' => 'group_id',
'joinTable' => 'AdminUsersGroups'
]);
I am returning records with this code:
public function getAll()
{
$ble = $this->find()
->contain(['AdminUserGroup' ]);
return $ble;
}
Until that it works, but when i want to select specified fields i have problem. When I add select method i don't see columns from contained table:
public function getAll()
{
$ble = $this->find()->select(['id', 'name', 'surname'])
->contain(['AdminUserGroup']);
return $ble;
}
So I added callback query:
public function getAll()
{
$ble = $this->find()->select(['id, name, surname'])
->contain(['AdminUserGroup' => function ($q) { return $q->select(['group_name']);}]);
return $ble;
}
But it still dont work. I can see only fields from main table. Fields with contained table doesn't appear.
{
"id": "8",
"name": "Ola",
"lastname": "BleBle",
"admin_user_group": []
},
Haw can I repair it?

The manual includes the following comments:
When you limit the fields that are fetched from an association, you must ensure that the foreign key columns are selected. Failing to select foreign key fields will cause associated data to not be present in the final result.
and
If you have limited the fields you are loading with select() but also want to load fields off of contained associations, you can pass the association object to select().
and
Alternatively, if you have multiple associations, you can use autoFields().
And there are helpful examples there. So:
$ble = $this->find()
->select(['id', 'name', 'surname'])
->contain(['AdminUserGroup' ])
->autoFields(true);
or if you're using 3.1 or higher:
$ble = $this->find()
->select(['id', 'name', 'surname'])
->select($this->AdminUserGroup);
From the example, it looks like the contain call may not be required in the second version, so I've left it out.

Try something like this.
$ble = $this->find()->select(['Table.id, Table.name, Table.surname'])->contain(['AdminUserGroup']);

Related

Laravel - sort array from other class by SQL table column

I am calling an array of all the comments of a poll by using the following code:
$poll = Poll::find($id);
return view('pages.poll', ['poll' => $poll, 'comments' => $poll->comments]);
and the links between Comments and Polls are the following:
Comment.php
public function poll() {
return $this->belongsTo(Poll::class, 'poll_id');
}
Poll.php
public function comments() {
return $this->hasMany(Comment::class, 'poll_id');
}
Finally, I would like to sort the array comments coming from $poll->comment by the column likes in the Comment table, something like DB::table('comment')->orderBy('likes')->get();.
Is there any way to do that?
$poll->comments->sortBy('likes')
There's a number of ways you can do this.
Add orderBy('likes') directly to your comments relationship:
Poll.php:
public function comments() {
return $this->hasMany(Comment::class, 'poll_id')->orderBy('likes');
}
Now, any time you access $poll->comments, they will be automatically sorted by the likes column. This is useful if you always want comments in this order (and it can still be overridden using the approaches below)
"Eager Load" comments with the correct order:
In your Controller:
$poll = Poll::with(['comments' => function ($query) {
return $query->orderBy('likes');
})->find($id);
return view('pages.poll', [
'poll' => $poll,
'comments' => $poll->comments
]);
with(['comments' => function ($query) { ... }]) adjusts the subquery used to load comments and applies the ordering for this instance only. Note: Eager Loading for a single record generally isn't necessary, but can be useful as you don't need to define an extra variable, don't need to use load, etc.
Manually Load comments with the correct order:
In your Controller:
$poll = Poll::find($id);
$comments = $poll->comments()->orderBy('likes')->get();
return view('pages.poll', [
'poll' => $poll,
'comments' => $comments
]);
Similar to eager loading, but assigned to its own variable.
Use sortBy('likes'):
In your Controller:
$poll = Poll::find($id);
return view('pages.poll', [
'poll' => $poll,
'comments' => $poll->comments->sortBy('likes')
]);
Similar to the above approaches, but uses PHP's sorting instead of database-level sorting, which can be significantly less efficient depending on the number of rows.
https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
https://laravel.com/docs/9.x/eloquent-relationships#constraining-eager-loads
https://laravel.com/docs/9.x/collections#method-sortby
https://laravel.com/docs/9.x/collections#method-sortbydesc

Getting specific column in Laravel relation returning empty array

I want to get specific column from Model relation in Laravel, but the relation model returning empty array.
Eloquent Query
$medicines = Medicine::with(['weightage' => function($query) {
$query->select('name');
}])->get(['name', 'description']);
Medicine Modal
public function weightage()
{
return $this->hasMany('App\MedicineWeightage', 'medicine_id');
}
You always need to also select the primary and foreign keys of the table to make the relation work:
$medicines = Medicine::with(['weightage' => function($query) {
$query->select(['id', 'name', 'medicine_id']);
}])->get(['id', 'name', 'description']);

How to select aggregates with Eloquent

I have 2 models: User and Transaction with user_id, type, and amount fields. I want to be able to select users with array of transactions grouped by type like so:
{ // User instance
'id': 123,
'username': 'John'
'transactions': [
'type1': '231', // `type`: `sum(amount)`
'type2': '543'
]
}
I will select arrays of users so I need this to load eagerly. How should I write an Eloquent model for that? I've read all docs but still don't know how to approach this.
You Can try Like :
$column = [DB::raw('SUM(order_qty) as volume'),
DB::raw('SUM(order_qty*price) as value')];
$oldValueVolume = OrderProduct::select($column)
->where('order_id', $ordersId)
->first();
User model:
public function transactions()
{
return $this->hasMany(Transaction::class, 'user_id');
}
So then the call:
User::with(
[
'transactions' => function ($q) {
return $q->groupBy(['type', 'user_id'])
->selectRaw('sum(amount) as sum, type, user_id');
}
]
)->get();
Notes
I used 'with' for eager loading.
You mentioned a custom structure (pairs) in a question:
This part
'transactions': [
'type1': '231', // `type`: `sum(amount)`
'type2': '543'
]
That's not what Eloquent does by-default, but you can always remap it.

Retrieve all models that are not associated with another model through pivot

I have three tables: users, organizations, organization_user. organization_user is a pivot table for the many-to-many relationship between users and organizations. The many-to-many relationships have been setup correctly in the corresponding models.
I need to obtain all users who are not associated with a given organization. How should this be done using eloquent. Below is what I have tried, but it is returning no results:
public function associate_user($organization_id){
$data = [
'organization' => \App\Organization::find($organization_id),
'users' => \App\User::whereDoesntHave('organizations', function($query) use ($organization_id){
$query->where('organization_id', $organization_id);
})
];
return view('admin.associateUser', $data);
}
You are never actually executing the query.
You need to call get() at the end of the query builder.
public function associate_user($organization_id) {
$data = [
'organization' => \App\Organization::find($organization_id),
'users' => \App\User::whereDoesntHave('organizations', function($query) use ($organization_id){
$query->where('organization_id', $organization_id);
})->get(); // Add the call to get()
];
return view('admin.associateUser', data);
}

Laravel - Unnecessary columns still being loaded with select statement

I am wanting to limit a controller's function's result to only pass certain columns into the view.
It is necessary because it will be used within an API, and so I need the results to be as streamlined as possible.
I have done this successfully with the following function:
public function getIndex()
{
$alerts = Criteria::select('id', 'user_id', 'coordinate_id', 'alert_name')
->with(['coordinate' => function($q){
$q->select('name', 'id');
}])
->get();
}
So it only returns id, user_id and coordinate_id from the criteria table.
However on the function below, I am using a has query (to access a relationship), and thus, using with afterwards to limit the columns, but it's still returning all:
public function getMatches()
{
$matches = Criteria::select('id')
->has('alerts')
->with(['alerts' => function ($q){
$q->select('id', 'headline', 'price_value', 'price_type');
}])
->with('alerts.user.companies')
->get();
}
But, for example, it's still returning the description column, which is in the alert's table. The with query proceeding the has query clearly isn't working (but it's presenting no errors).
Also, the ->with('alerts.user.companies') query, is returning everything within the user's table, which is also unnecessary. How can I return just the companies table data, that's related to the user, who's related to the alert?
Your help would be greatly appreciated.
Depending what you want to achieve, you could use $hidden property to hide columns you don't want to return as json or arrays.
In your Alert model you could do:
protected $hidden = ['description'];
And this way description field won't be returned.
If it's not the way for you (sometimes you want to return description) you could create extra relationships where you limit fields from database.
You could for example create the following relationship:
public function alertsSimple() {
return $this->hasMany('Alert')->select('id', 'headline', 'price_value', 'price_type', 'criteria_id');
}
Also maybe in your select the problem is that you don't use foreign key at all. You could also try with:
$q->select('id', 'headline', 'price_value', 'price_type','criteria_id');
instead of
$q->select('id', 'headline', 'price_value', 'price_type');

Categories