I have a specific request, to build an association between users. This causes me confusion, how to reduce duplicate associations, query and results?
The starting point would look like this?
// UsersTable
$this->belongsToMany('Users', [
'through' => 'Connections',
]);
How to fetch all associations in one query, regardless of whether users key in "user_from" or "user_to" field?
How about using aliases?
Your users table:
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('ToConnections', [
'className' => 'Connections',
'foreignKey' => 'user_to'
]);
$this->hasMany('FromConnections', [
'className' => 'Connections',
'foreignKey' => 'user_from'
]);
}
}
And your connections table:
class ConnectionsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('ToUsers', [
'className' => 'Users',
'foreignKey' => 'user_to'
]);
$this->belongsTo('FromUsers', [
'className' => 'Users',
'foreignKey' => 'user_from'
]);
}
}
You can then use contain() to load associated models as required.
$query = $conections->find()->contain([
'ToUsers',
'FromUsers'
]);
$recipients = TableRegistry::get('users');
$query = $recipients->find()->contain([
'ToConnections.FromUsers',
]);
Related
I have master table portfolios and there are 2 child tables portfoliotags and p_snaps
models are as below
class PortfoliosTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('portfolios');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->hasMany('PSnaps', [
'foreignKey' => 'portfolio_id',
]);
$this->hasMany('PortfolioTags', [
'foreignKey' => 'portfolio_id',
]);
}
}
class PortfolioTagsTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('portfolio_tags');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Portfolios', [
'foreignKey' => 'portfolio_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Tags', [
'foreignKey' => 'tag_id',
'joinType' => 'INNER',
]);
}
}
class PSnapsTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('p_snaps');
$this->setDisplayField('title');
$this->setPrimaryKey('id');
$this->belongsTo('Portfolios', [
'foreignKey' => 'portfolio_id',
'joinType' => 'INNER',
]);
}
}
find() method in controller is as below
$pfolios = $this->Portfolios->find('all')
->select(['Portfolios.id','Portfolios.client','Portfolios.country','Portfolios.url'])
->where(['Portfolios.status'=>1])
->order(['Portfolios.order_at'=>'asc','Portfolios.id'=>'asc'])
->limit(8)
->contain([
'PSnaps'=>function($q){
return $q
->select(['PSnaps.portfolio_id','PSnaps.snap'])
->where(['PSnaps.status'=>1])
->order(['PSnaps.order_at'])
->limit(1);
},
'PortfolioTags.Tags'=>function($q2){
return $q2
->order(['Tags.tag']);
}
])
->toList();
debug($pfolios);exit;
It is retuning PSnaps inside first record of Portfolio, while in all other records it is empty array
while I know there are records in database I tried using below query in mysql too
SELECT p.id,s.snap FROM `portfolios` p INNER join p_snaps s on p.id=s.portfolio_id
it is returning records as below
id snap
1 s1.png
2 pers.png
3 gmap.png
4 ita.png
5 soapd.png
6 chat.png
7 aissmo.png
8 zippy.png
2 pereport.png
This is because of limit() inside PSnaps, if it is commented it will show all PSnap records under each Portfolios records.
To handle this situation I fix it by adding hasOne relation in PortfoliosTable.php as
$this->hasOne('FirstPSnaps', [
'className' => 'PSnaps',
'foreignKey' => 'portfolio_id',
'strategy' => 'select',
'sort' => ['FirstPSnaps.order_at' => 'ASC'],
'conditions' => function ($e, $query) {
return [];
}]);
now call in controller will be changed to as below
$pfolios = $this->Portfolios->find('all')
->select(['Portfolios.id','Portfolios.client','Portfolios.country','Portfolios.url'])
->where(['Portfolios.status'=>1])
->order(['Portfolios.order_at'=>'asc','Portfolios.id'=>'asc'])
->limit(8)
->contain([
/********instead of PSnap I used FirstPSnaps *********/
'FirstPSnaps'=>function($q){
return $q
->select(['FirstPSnaps.portfolio_id','FirstPSnaps.snap'])
->where(['FirstPSnaps.status'=>1])
->order(['FirstPSnaps.order_at']);
},
'PortfolioTags.Tags'=>function($q2){
return $q2
->order(['Tags.tag']);
}
])
->toList();
debug($pfolios);exit;
I have created a table called Delegates, and I created a UsersTable and User Entity. And I have used $this->setTable('delegates'); in UsersTable to be able to access Delegates from $this->Users; (I just want to say I have created a User Model with delegates table)
So far so good...
In my application I am trying to access deep associations. Every thing is fine with this query but when I contain the User model I get The Users association is not defined on Comments.
I can confirm the associations are set correctly.
...
// There are more associations up there.
...
'Comments', [
'className' => 'App\Model\Table\CommentsTable',
'foreignKey' => 'delegate_assessment_criteria_id',
'belongsTo' => [
'Assessors' => [
'className' => 'App\Model\Table\AssessorsTable',
'foreignKey' => 'assessor_id',
],
'Users' => [
'className' => 'App\Model\Table\UsersTable',
'foreignKey' => 'delegate_id',
]
]
]
Here is deep association.
...
// There are more associations up there.
...
'Comments' => function($q) {
return $q
->select([
'id'
])
->order(['Comments.created DESC'])
->contain([
'Assessors' => function($q) {
return $q
->select([
'id'
])
->enableAutoFields();
}
])
->contain([
'Users' => function($q) {
return $q
->select([
'id',
'delegate_id'
])
->enableAutoFields();
}
])
->enableAutoFields();
}
2 Notes:
If I contain the User model in very first in hierarchy of my query I
can access the User fields but in deep association this doesn't
work.
If I contain Delegates it works.
I believe there is a problem with Cakephp query builder.
Alright finally I figured it out. I have done it before I don't know why I forgot. Probably because I was in deep association I drained into it.
The deep association contain was creating conflict with very first contain. if I would set different propertyName in deep associations then it does the purpose.
Bear in mind you must set this associations on your Table Models.
'Comments', [
'className' => 'App\Model\Table\CommentsTable',
'foreignKey' => 'delegate_assessment_criteria_id',
'belongsTo' => [
'Assessors' => [
'className' => 'App\Model\Table\AssessorsTable',
'foreignKey' => 'assessor_id',
],
'Delegates' => [ // <= Here set the name of Assossiation you want to be shown in array
'className' => 'App\Model\Table\UsersTable',
'propertyName' => 'delegates', // <= Add propertyName and set another name here
'foreignKey' => 'delegate_id',
]
]
]
And on association
'Comments' => function($q) {
return $q
->select([
'id'
])
->order(['Comments.created DESC'])
->contain([
'Assessors' => function($q) {
return $q
->select([
'id'
])
->enableAutoFields();
}
])
->contain([
'Users' => function($q) {
return $q
->select([
'id',
// 'delegate_id' // <= Remove this - We have already done it when we set the association on above code.
])
->enableAutoFields();
}
])
->enableAutoFields();
}
I’m struggling with the hasOne association. Here are my 2 tables :
Actually the ‘Etudiant’ (means student) has one User associated to it with the foreign key id present in both table which is the primary key of Users.
Here is the model EtudiantTable (student) :
class EtudiantTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('etudiant');
$this->setDisplayField('ETUDIANT_ID');
$this->setPrimaryKey('ETUDIANT_ID');
$this->addBehavior('Timestamp');
$this->belongsTo('Maitredestage', [
'foreignKey' => 'MAITREDESTAGE_ID',
'joinType' => 'INNER'
]);
$this->belongsToMany('Tuteuriut', [
'foreignKey' => 'ETUDIANT_ID',
'targetForeignKey' => 'TUTEURIUT_ID',
'joinTable' => 'Suivre'
]);
$this->belongsTo('Users', [
'foreignKey' => 'ID',
'joinType' => 'INNER'
]);
}
And here is the add function in the EtudiantController :
public function add()
{
$etudiant = $this->Etudiant->newEntity();
if ($this->request->is('post')) {
$etudiant = $etutable->patchEntity($etudiant, $this->request->data(), [
'associated' => ['Users']
]);
if ($this->Etudiant->save($etudiant)){
$this->Flash->success(__('The etudiant has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The etudiant could not be saved. Please, try again.'));
}
$this->set(compact('etudiant'));
}
And my usersTable model is like this :
$this->belongsTo('Users', [ 'foreignKey' => 'ID', 'joinType' => 'INNER' ]);
I want to do this : when i insert an Etudiant it inserts a new user and both have the same id.
But currently when i insert an Etudiant i get an error saying “no default value…id is empty” but the id is supposed to be inserted automatically.
I'm completely stuck so i need your help. Thanks in advance.
I am using hasOne association.Here my code for UserMastersTable :
class UserMastersTable extends Table {
public function initialize(array $config) {
parent::initialize($config);
$this->table('user_masters');
$this->hasOne('PersonMasters', [
'className' => 'PersonMasters',
'foreign_key' => 'user_master_id',
'conditions' => ['PersonMasters.status' => 1],
'dependent' => true,
]);
} }
When use find() in my controller.It fetch all user_masters data and person_masters data whose status ='1'.
but problem is that i already assign condition where association bind..already give condition that only display that data whose person_masters.status=1.
so why it shows all data of user_masters ?
if i give condition in find() in controller then it works fine..
$this->UserMasters->find('all',
['contain' =>
['PersonMasters'],
'conditions' =>
['PersonMasters.status' => 1]
]);
so, how can i globally give condition that only fetch data of user_masters and person_masters where PersonMasters.status=1?
Try this might be it will resolve your issue
$this->UserMasters->find('all',[
'contain' =>
['PersonMasters' => [
'conditions' => ['status' => 1]
]
],
]);
Trying to set up an example on how to use join table with extra data I have the following set:
table students: id, name, [...]
table courses: id, title, [...]
join table courses_students: id, course_id, student_id, grade, hours_attended
The two base table's :
class StudentsTable extends Table {
public function initialize(array $config) {
$this->belongsToMany('Courses', [
'alias' => 'Courses',
'foreignKey' => 'student_id',
'targetForeignKey' => 'course_id',
'joinTable' => 'courses_students',
'through' => 'CoursesStudents',
]);
}
class CoursesTable extends Table {
public function initialize(array $config) {
$this->belongsToMany('Students', [
'alias' => 'Students',
'foreignKey' => 'course_id',
'targetForeignKey' => 'student_id',
'joinTable' => 'courses_students',
'through' => 'CoursesStudents',
]);
}
And the association table:
class CoursesStudentsTable extends Table {
public function initialize(array $config) {
$this->belongsTo('Courses', [
'alias' => 'Courses',
'foreignKey' => 'course_id'
]);
$this->belongsTo('Students', [
'alias' => 'Students',
'foreignKey' => 'student_id'
]);
}
Having some courses available in the table, I try to add and edit student records. Setting
[courses] => [_ids]
in the student record creates the records in both students table and the association table.
How should the post data array be formed in order to be able to store the grade and hours_attended fields in the association table when saving the student record?
You should configure your form field as the following assuming you are in the Courses form.
echo $this->Form->create($courses);
echo $this->Form->input("Courses.id");
echo $this->Form->input("Courses.title");
echo $this->Form->input("Courses.courses_students.$i.grade");
echo $this->Form->input("Courses.courses_students.$i.hours_attended");
The basic idea is that your forms should exactly follow how the data is formatted when retrieved in your controller.
This will then format your data correctly for you.
Then in your controller, you'll need to pass the associations to patch your entity.
$courses = $this->Courses->patchEntity($this->request->data(), ['associations' => ['CoursesStudents']]);
This will merge your associated request data in your entity, so you can save it.