Yii2 build relation many to many
I have 2 tables users and friends
Code query
$friends = Friends::find()
->select(['friends.user_id', 'users.name'])
->leftJoin('users','users.id = friends.friend_user')
->with('users')
->all();
In result error
Invalid Parameter – yii\base\InvalidParamException. app\models\Friends has no relation named "users".
Friends has a column called user_id and thus only belongs to one user. If you auto-generated the Friends ActiveRecord it probably has a function getUser (singular because it is only one) that will look something like this:
public function getUser() {
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
So you're getting the error because no getUsers function exists (that returns a valid ActiveQuery object). Because there can only be one user per friend I think you should use the singular version. And if that still gives the same error you should implement the function above and maybe change it a bit to match your classname.
When you use with(['relation']) to load relations Yii will convert the entry to getRelation and call that function on the model to get the query that is needed to load the relation.
Related
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.
How do write this eloquent query in Laravel so that it eager loads with() the relationship model in this example between a User model and Profile model? I was trying to avoid 2 separate queries.
I feel I am close, but somethings not quite right.
$author = User::where('id', $id)->with('profile')->get();
The collection is returning the user details correctly. But it's showing the profile relationship as null.
#relations: array:1 [▼
"profile" => null
]
I believe I have things setup correctly with a User model and a Profile needed relationships.
User.php
public function profile()
{
return $this->hasOne('App\AuthorProfile', 'user_id');
}
AuthorProfile.php
public function user()
{
return $this->belongsTo('App\User');
}
Assuming for AuthorProfile model table you have record with id of user it should be fine.
However you wrote:
I was trying to avoid 2 separate queries.
Well, it's not true, if you have single record, eager loading won't help you at all. In this case 2 queries will be execute - no matter if you use eager loading or you won't.
Eager loading would help if you had multiple users and for each of them you wanted to load profile, but if you have single record it won't change anything.
Additionally instead of:
$author = User::where('id', $id)->with('profile')->get();
you should rather use:
$author = User::with('profile')->find($id);
because you expect here single user.
$users = User::with('profile')->find($id);
Your model should be like this.The User_id on the profile table and id on the user table
public function profile()
{
return $this->hasOne('App\AuthorProfile', 'user_id','id');
}
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.
I'm new to cakephp. I'm trying to search through mysql tables. I want to use nested query.
class TableController extends AppController{
.
.
public function show(){
$this->set('discouns', $this->DiscounsController->query("SELECT * FROM discoun as Discoun WHERE gcil_id = 1"));//(SELECT id FROM gcils WHERE genre = 'Shoes' AND company_name = 'Adidas')"));
}
}
Error:
Error: Call to a member function query() on a non-object
I've also tried
public function show(){
$this->DiscounsController->query("SELECT * FROM count as Count WHERE ctr_id = (SELECT id FROM ctrs WHERE genre = 'Shoes' AND company_name = 'Adidas')");
}
Error:
Error: Call to a member function query() on a non-object
File: C:\xampp\htdocs\cakephppro\myapp\Controller\CountsController.php
Please help. I've been trying this for last few hours. :/
As mentioned in the comments there are a few problems with your code.
Firstly, you are trying to call the query() method on a Controller, whereas you should be executing it on a Model, as it is models that handle database queries and the controller should simply be used to call these methods to get the data and pass them to the view.
The second thing is that you are executing a very simple SQL query raw instead of using CakePHPs built in functions <- Be sure to read this page in full.
Now for your problem, as long as you have setup your model relationships correctly and followed the correct naming conventions, this should be your code to run your SQL query from that controller:
public function show(){
$this->set('discouns', $this->Discouns->find('all', array(
'conditions' => array(
'gcil_id' => 1,
'genre' => 'shoes',
'company_name' => 'Adidas'
)
));
}
query() is not a Controller, but a Model method. That's what the error (Call to a member function on a non-object) is trying to tell you.
So the correct call would be:
$this->Discount->query()
But you are calling this in a TableController, so unless Table and Discount have some type of relationship, you won't be able to call query().
If the Table does have a relationship defined you will be able to call:
$this->Table->Discount->query()
Please not that query() is only used when performing complex SQL queries in scenarios where the standard methods (find, save, delete, etc.) are less practical.
$this->Counts->find('all',array(
'conditions' => array(
'ctrs.genre' => 'Shoes',
'ctrs.company_name' => 'Adidas'
), 'recursive' => 1
));
The above is with tables named counts and ctrs.
This is assuming you have the model set up to have some sort of relationship between the counts table and the ctrs table. It's kind of hard to tell in your code exactly what you tables are.
The CakePHP book should have all the answers you need. One of the reasons to run CakePHP over regular PHP is the FIND statement. Once you have your models set up correctly, using the find statement should be really easy.
http://book.cakephp.org/2.0/en/models.html
Im using the Repository pattern and I want to write a method that receives a role and returns an Iqueryable of the users that belong to that role. (Im not sure if the right way would be to receive the role object or the role_id... in any case, how can I do this?? I dont like the query structure, I prefer the method structure of linq.
users and roles is many to many with a users_roles join table.
private ClasesDataContext db = new ClasesDataContext();
public IQueryable GetByRole(Role role)
{
return db.Users.Where();
}
Maybe try something like:
public IQueryable<User> GetByRoleId(Role role) {
return db.UsersRoleJoinTable.Where(ur => ur.Role == role).select(ur => ur.User);
}
Where UsersRoleJoinTable is your many-to-many join table.
Hope it helps.
Update: the select(ur => ur.User) is telling linq that for every row returned by "db.UsersRoleJoinTable.Where(ur => ur.Role == role)" we want to get the user associated with the UsersRoleJoinTable object. If you wanted a list of user ids instead, you could tell linq to select only user.id by doing select(ur => ur.id). Think of linq's select as a some sort of "for every row do this and put it in the list returned instead of the original row"
There is one downside to this approach tho, I believe in this case Linq is generating the sql to get the rows from the Join table (UsersRoleJoinTable) and then for every row returned, is executing another query to look up the User. I might be wrong on this, so to check the SQL generated by Linq do:
string sql_query = db.UsersRoleJoinTable.Where(ur => ur.Role == role).select(ur => u.User).ToString();
and then print the value of sql_query or watch it in debug mode. If Linq is in fact doing multiple queries, then I think the best solution is to create a view or stored procedure in SQL Server to get the users associated with the role and then add the view or stored procedure to Visual Studio designer so that you can call the view like:
db.GetUsers(role_id) //if using a GetUsers stored procedure
or
db.UsersByRoleView.where(ur => ur.role_id == passed_role_id) //if using a UsersByRoleView view
If you have an instance of the Role object
public IQueryable<User> GetByRole(Role role) {
return db.Users.Where(u => u.Role == role);
}
would work.
If you don't but just know the Id or some other property of the role something like this might be better.
public IQueryable<User> GetByRoleId(int roleId) {
return db.Users.Where(u => u.Role.Id == roleId);
}