Struggling to save in 2 tables with hasOne association with cakePHP 3 - cakephp

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.

Related

cakephp 4 find method with contain is skipping some records from subtable

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;

How to change the join type of a contained association per find() call?

How to reset the type of joins in different places?
Here are my tables:
Tenancy Table
class TenancyTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('tenancy');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Properties', [
'foreignKey' => 'property_id',
'className' => 'property',
'joinType' => 'INNER'
]);
Property Table
class PropertyTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('property');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Tenancies', [
'foreignKey' => 'property_id',
'className' => 'tenancy',
'joinType' => 'LEFT'
]);
For example change the Tenancy join type to Right
$setting = [
'contain' => [
'Tenancies' => [
'joinType' => 'RIGHT'
]
]
];
$properties = $this->find('all', $setting)
->hydrate(false)
->select(['Property.id', 'Property.company_id', 'Property.address1', 'Property.postcode'])
You would do it exactly the way you are showing it, ie by specifying the joinType option in contain settings.
However, since you are querying from the Property table, this will have no effect, as hasMany associations are being retrieved in a separate query, so no joins involved. It would work for hasOne and belongsTo associations, which are being retrieved in the same query.

CakePHP 3 - Users belongsToMany Users

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',
]);

How to save associated joinData in cakephp 3.x

I have Problems and Fields in a many-to-many relationship. The join table fields_problems has a field named fieldvalue I am trying to have a form that will insert a problem record and also multiple records into fields_problems.
/src/Model/Table/ProblemsTable.php
class ProblemsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('problems');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Fields', [
'foreignKey' => 'problem_id',
'targetForeignKey' => 'field_id',
'joinTable' => 'fields_problems'
]);
}
...
/src/Model/Table/FieldsTable.php
class FieldsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('fields');
$this->displayField('name');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Problems', [
'foreignKey' => 'field_id',
'targetForeignKey' => 'problem_id',
'joinTable' => 'fields_problems'
]);
}
...
/src/Model/Table/FieldsProblemsTable.php
class FieldsProblemsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('fields_problems');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Fields', [
'foreignKey' => 'field_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Problems', [
'foreignKey' => 'problem_id',
'joinType' => 'INNER'
]);
}
...
And I want to Add a new problem, link it to fields, and add values to the fieldvalue field in the join table.
So I have this /src/Template/Problems/add.ctp
<div class="problems form large-10 medium-9 columns">
<?= $this->Form->create($problem) ?>
<fieldset>
<legend><?= __('Add Problem') ?></legend>
<?php
echo $this->Form->input("Problems.id");
echo $this->Form->input('Problems.summary');
echo $this->Form->input('Problems.Fields.0._ids', [
'type' => 'select',
'multiple' => false,
'options' => $fields,
]);
echo $this->Form->input('Problems.Fields.0._joinData.fieldvalue');
echo $this->Form->input('Problems.Fields.1._ids', [
'type' => 'select',
'multiple' => false,
'options' => $fields,
]);
echo $this->Form->input('Problems.Fields.1._joinData.fieldvalue');
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>
And this add() in /src/Controller/ProblemsController.php
public function add()
{
$problem = $this->Problems->newEntity();
if ($this->request->is('post')) {
$problem = $this->Problems->patchEntity($problem, $this->request->data, ['associated'=>['Fields._joinData']] );
//$problem->dirty('fields',true);
if ($this->Problems->save($problem)) {
$this->Flash->success(__('The problem has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The problem could not be saved. Please, try again.'));
}
}
$fields = $this->Problems->Fields->find('list', ['limit' => 200]);
$this->set(compact('problem', 'fields'));
$this->set('_serialize', ['problem']);
}
When I fill out and submit the app form, the Problem record is saved, but the association is not, nothing gets inserted into fields_problems.
What am I doing wrong that is preventing the associated joinData from being saved?
Despite the cookbook (http://book.cakephp.org/3.0/en/views/helpers/form.html) saying to use the special _ids key, don't!
Changing "_ids" to "id" fixed the form and now it functions properly saving the data into the jointable.
Here is the example from the cookbook that I built my app with
echo $this->Form->input('tags.0.id');
echo $this->Form->input('tags._ids', [
'type' => 'select',
'multiple' => true,
'options' => $tagList,
]);
Here is how it should be
echo $this->Form->input('tags.0.id', [
'type' => 'select',
'multiple' => false,
'options' => $tagList,
]);

Cakephp 3, Saving form data on association tables

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.

Resources