Yii 2 Active Query joinWith issue - yii2

I have a Active Record model "Event" with a hasOne relation 'getUser'.
Now, if I do :
$eventModels = Event::find()->joinWith([
'user' => function($q){
return $q;
}])->all();
----------------------------------------------------
foreach($eventModels as $m){
var_dump($m->user); //Everything good as $m->user returns the related user object
die('skdw');
}
But, if I add the "select" in the joinWith query, then related "user" object becomes null. Here is the issue :
$eventModels = Event::find()->joinWith([
'user' => function($q){
$q->select('email');// or, ['email'] or ['user.email'] etc. fields.
return $q;
}])->all();
----------------------------------------------------
foreach($eventModels as $m){
var_dump($m->user); // Returns NULL
die('skdw');
}
But, if I make it $q->select('*'), then $m->user working .
I believe it used to work in some previous versions of Yii 2 (Right now, I am working on Yii 2.0.9)
Is this expected behavior ? If yes, then what is the solution to fetch only some select fields for the related joinWith model ? I don't want to fetch all the related fields as some of the related fields might contain "TEXT" data type.

You need to select the primary key column for the relation for Yii to build them.
e.g. assuming your column is called id
$eventModels = Event::find()->joinWith(['user' => function($q){
$q->select(['id', 'email']);
}])->all();
Also, you don't need to return the $q variable.

Related

Laravel Many To Many Merges Pivot?

I must be going insane or be really tired. So I have this situation where I get a collection of all the Roles assigned to the User. That part goes ok.... however I noticed something super strange.
I am using Laravel 8 and PHP8 (not the strange part).
For some reason, I do not get only the result from the other table but also pivot data is merged in. I can't tell why this is happening. Here is the example:
Relationship on user model:
/**
* Relationship with roles model.
*
* #return BelongsToMany
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(
Role::class,
'role_user',
'user_id',
'role_id'
)->withTimestamps();
}
Relationship on the Role model:
/**
* Relationship with users table.
*
* #return BelongsToMany
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(
User::class,
'role_user',
'role_id',
'user_id'
)->withTimestamps();
}
In the user model, I have this.
$this->roles->each(function($role) {
dd($role);
});
I was expecting to get a dump of related model however for some weird reason what I get is pivot table merged with the model:
"id" => 7 // this is the relation ID from the pivot table
"display_name" => "Administrator" // this is from Role model
"code" => "admin" // role model
"description" => "Super User - can do everything in the system. This role should only be assigned to IT staff member." // role model
"created_at" => "2021-10-01 11:00:00" // pivot table
"updated_at" => null // pivot table
"deleted_at" => null // pivot table
"role_id" => 1 // pivot table
"user_id" => 2 // pivot table
Either I am doing something very wrong or I am missing something very obvious. Does anyone know what in the world is happening here?
Just to add: the data is from both places but the result is just a Role model as expected.
Should I not just get the role model without the pivot stuff in it? It is overriding my role model fields.
EDIT:
Parenthesis seems to make a difference. The data is still merged. However, when I do it like this looks like data from end model is merged (so it overrides) to data from the pivot. So I get correct ID.
$this->roles()->each(function($role) {
echo $role;
});
But this gives me this weird pivot merged version with wrong ID.
$this->roles->each(function($role) {
echo $role;
});
I know what that was exactly. Without thinking I've added the ID column into the pivot table.
This ID from pivot was overriding my ID from my end model. After I've removed it the problem is gone.
I don't know why Laravel would by default add these fields and merge with pivot columns... I guess it just does that for no reason. Although I don't understand what's the point if there is a separate mechanism to access the pivot table (pivot relationship on the model).
This makes me think I did something wrong. But yeah, hope it helps. If anyone knows why Laravel automatically adds pivot stuff, let me know.

Yii2: How to access a table column via a relation

I have two tables, dpd and cabang. One cabang can only have one dpd and each cabang should have a dpd.
This is the relation in my "Cabang" model :
public function getIdDpd()
{
return $this->hasOne(Dpd::className(), ['id_dpd' => 'id_dpd']);
}
I try to access "dpd" attribute in "dpd" table in my view, I tried to var_dump it but still got the same error :
$model = Cabang::find()
->joinWith('idDpd')
->all();
var_dump($model->dpd);
Note : 'dpd' in var_dump($model->dpd) refered to the column name in 'Dpd' table. I have a column name 'dpd' in my 'dpd' table. I also tried $model->idDpd->dpd but it return the same error.
What am I doing wrong? Thanks
Your mistake is
$model->dpd
where you are trying to access it assuming that $model has the Dpd object, but it is holding the Cabang object and you should use the relation name to get the Dpd object and then call the field name like below.
$model->idDpd->dpd
EDIT: You are using ->all() in your query not ->one() and hence you cannot simply do $model->idDpd->dpd right after the query, you need to loop through the records and then call the relation, i thought you might be doing the same way but anyway see below how to use it.
$cabangs = Cabang::find()
->joinWith('idDpd')
->all();
foreach($cabangs as $index=>$model){
$model->idDpd->dpd;
}

Multiple Fields with a GroupBy Statement in Laravel

Already received a great answer at this post
Laravel Query using GroupBy with distinct traits
But how can I modify it to include more than just one field. The example uses pluck which can only grab one field.
I have tried to do something like this to add multiple fields to the view as such...
$hats = $hatData->groupBy('style')
->map(function ($item){
return ['colors' => $item->color, 'price' => $item->price,'itemNumber'=>$item->itemNumber];
});
In my initial query for "hatData" I can see the fields are all there but yet I get an error saying that 'colors', (etc.) is not available on this collection instance. I can see the collection looks different than what is obtained from pluck, so it looks like when I need more fields and cant use pluck I have to format the map differently but cant see how. Can anyone explain how I can request multiple fields as well as output them on the view rather than just one field as in the original question? Thanks!
When you use groupBy() of Laravel Illuminate\Support\Collection it gives you a deeper nested arrays/objects, so that you need to do more than one map on the result in order to unveil the real models (or arrays).
I will demo this with an example of a nested collection:
$collect = collect([
collect([
'name' => 'abc',
'age' => 1
]),collect([
'name' => 'cde',
'age' => 5
]),collect([
'name' => 'abcde',
'age' => 2
]),collect([
'name' => 'cde',
'age' => 7
]),
]);
$group = $collect->groupBy('name')->values();
$result = $group->map(function($items, $key){
// here we have uncovered the first level of the group
// $key is the group names which is the key to each group
return $items->map(function ($item){
//This second level opens EACH group (or array) in my case:
return $item['age'];
});
});
The summary is that, you need another loop map(), each() over the main grouped collection.

Custom finder selecting fields in associated model Cakephp3

I have an Event model hasMany Attendances with event_id in the attendances. I want to select some fields in attendance table in my custom find method but the contain() method doesn't join the two tables.
public function findAttendanceDetails(Query $query)
{
return $query->contain('Attendances')
->select(['Gender' => 'Attendances.gender',
'Name' => 'Attendances.full_name'
]);
}
I am getting an error of Error: SQLSTATE[42S22]: Column not found: 1054 Champ 'Attendances.gender' inconnu dans field list and wondering what is missing.
When I use the ->Join() method instead of ->contain(), I get the results.
Contain doesn't quite work that way. You would be looking for something like:
return $query->contain(['Attendances']);
Which will just grab and return the full associated table Attendances. Contain looks for an array, not a string value.
If you want specific fields from the associated table, you could try:
return $query->contain(['Attendances' => function ($q) {
return $q
->select(['gender', 'full_name'])
}
]);
You can read more about using contain here.

How to add combined unique fields validator rule in Laravel 4

I am using Laravel 4.2 and mysql db . I have an exam table in which i am taking Exams entry and the fields are --> id | examdate | batch | chapter | totalmarks
I have made a combined unique key using $table->unique( array('examdate','batch','chapter') ); in schema builder.Now I want to add a validation rule to it. I know i can add unique validation by laravel unique validator rule but the problem is ,it checks only for one field . I want it to add uniqueness to the 3 fields combined(user must not be able to add second row with same value combination of examdate,batch and chapter fields).
Is it even possible to do it in laravel 4 .Is there any workaround if its not possible?
You could write a custom validator rule. The rule could look something like this:
'unique_multiple:table,field1,field2,field3,...,fieldN'
The code for that would look something like this:
Validator::extend('unique_multiple', function ($attribute, $value, $parameters)
{
// Get table name from first parameter
$table = array_shift($parameters);
// Build the query
$query = DB::table($table);
// Add the field conditions
foreach ($parameters as $i => $field)
$query->where($field, $value[$i]);
// Validation result will be false if any rows match the combination
return ($query->count() == 0);
});
You can use as many fields as you like for the condition, just make sure the value passed is an array containing the values of the fields in the same order as declared in the validation rule. So your validator code would look something like this:
$validator = Validator::make(
// Validator data goes here
array(
'unique_fields' => array('examdate_value', 'batch_value', 'chapter_value')
),
// Validator rules go here
array(
'unique_fields' => 'unique_multiple:exams,examdate,batch,chapter'
)
);
It didn't work for me so I adjusted the code a tiny bit.
Validator::extend('unique_multiple', function ($attribute, $value, $parameters, $validator)
{
// Get the other fields
$fields = $validator->getData();
// Get table name from first parameter
$table = array_shift($parameters);
// Build the query
$query = DB::table($table);
// Add the field conditions
foreach ($parameters as $i => $field) {
$query->where($field, $fields[$field]);
}
// Validation result will be false if any rows match the combination
return ($query->count() == 0);
});
The validator looks like this. You don't need a particular order of DB table column names as stated in the other answer.
$validator = Validator::make($request->all(), [
'attributeName' => 'unique_multiple:tableName,field[1],field[2],....,field[n]'
],[
'unique_multiple' => 'This combination already exists.'
]);