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.
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’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 have a table name countries , countries has relation with users, users has relation with MoneyTransferTransactions
In MoneyTransferTransactions view I need fetch country name.
I already join Users table in MoneyTransferTransactionsTable by below code
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
In user table I also used inner join like below code
$this->belongsTo('Countries', [
'foreignKey' => 'country_id',
'joinType' => 'INNER'
]);
In MoneyTransferTransactions controller I have used below code to fetch all data with associated data.
$this->paginate = [
'contain' => ['Users','TransferOptions'],
'conditions'=> ['MoneyTransferTransactions.status'=>1],
'order' =>['MoneyTransferTransactions.id'=>'DESC']
];
I have used var_dump in index.ctp , I gotted country id from users table but not got country name from countries table. How can I get country name from MoneyTransferTransactions>index.ctp ?
Add association between Users and Countries in 'contain'
$this->paginate = [
'contain' => ['TransferOptions', 'Users' => ['Countries']],
'conditions' => ['MoneyTransferTransactions.status' => 1],
'order' => ['MoneyTransferTransactions.id' => 'DESC']
];
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',
]);
I am trying to get contain the Children of my DomainTypes table for my DomainTypes view() function but it is complaining with "Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Children.domain_type_id' in 'where clause'" The problem is that my DomainTypes table doesn't have a domain_type_id column. The Domain table does. Here is the code for my DomainTypes view():
public function view($id) {
if (!$id) {
throw new NotFoundException(__('Invalid DomainType'));
}
$domainType = $this->DomainTypes
->find()
->where(['DomainTypes.id' => $id])
->contain(['Parent', 'Children', 'Affiliates'])
->first();
$this->set(compact('domainType'));
}
So a little about my setup. I have 2 tables, Domains and DomainTypes. Both use the Tree behavior.
Here is the initialize code for the Domain Table:
public function initialize(array $config){
parent::initialize($config);
$this->displayField('name');
$this->addBehavior('Tree');
//Associations
$this->hasMany('Children', [
'className' => 'Domains',
]);
$this->belongsTo('Parent', [
'className' => 'Domains',
]);
$this->belongsTo('Affiliates');
$this->belongsTo('DomainTypes');
}
And here is the initialize code for the DomainTypes Table:
public function initialize(array $config){
parent::initialize($config);
$this->displayField('name');
$this->addBehavior('Tree');
//Associations
$this->hasMany('Children', [
'className' => 'DomainTypes',
]);
$this->belongsTo('Parent', [
'className' => 'DomainTypes',
]);
$this->belongsTo('Affiliates');
$this->hasMany('Domains');
}
Both very similar but also clearly defining which className to use. Why is Cakephp 3 assuming there is a domain_type_id column on my DomainTypes table? Thanks in advance!
We were both close, but not quite right. ndm, your answer was helpful though as it did point to another mistake I was making. The final association code should look like this:
$this->hasMany('Children', [
'className' => 'DomainTypes',
'foreignKey' => 'parent_id',
]);
$this->belongsTo('Parent', [
'className' => 'DomainTypes',
]);
When I was trying to enter the foreign key, Instead of "foreignKey", I was using "foreign_key" which was pretty much useless. Thanks for the help ndm.
Update I wasn't paying attention properly, and CakePHP seems to be actually correct when it auto-generates a foreign key named domain_type_id, it's a hasMany association and so the FK should be based on the source table.
The solution is still the same, the FK needs to be set explictly, and as already figured by #MjGaiser the parent_id for the belongsTo association isn't necessary, and instead the FK for the hasMany association should use parent_id instead of child_id:
$this->hasMany('Children', [
'className' => 'DomainTypes',
'foreignKey' => 'parent_id'
]);
$this->belongsTo('Parent', [
'className' => 'DomainTypes'
]);