CakePHP - belongsTo Data doesn't get saved to Join-Table - cakephp-3.0

I have 3 Tables involved: UsersTable, RolesTable, UsersRolesTable
I want to assign a User a given Role when signing up, but it doesn't save the Data to the join-table(UsersRolesTable) and I don't know why.
Following associations are present:
UsersTable
$this->belongsTo('Roles', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'role_id',
'joinTable' => 'users_roles'
]);
RolesTable
$this->belongsToMany('Users', [
'foreignKey' => 'role_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'users_roles'
]);
UsersRolesTable
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'joinType' => 'INNER'
]);
In my Users-controller I have the following function
public function register()
{
$roles = TableRegistry::get('Roles');
$role_data = $roles->findByName('User')->first();
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$data = $this->request->getData();
$user->profile = $this->Users->setDefaultProfile();
$user->role = $role_data;
$user = $this->Users->patchEntity($user, $data);
if ($this->Users->save($user)) {
$this->Flash->success(__('Ihr Account wurde erfolgreich angelegt. Sie können sich nun einloggen.'));
return $this->redirect(['action' => 'login']);
}
$this->Flash->error(__('Ihr Account konnte nicht angelegt werden. Bitte versuchen Sie es später erneut.'));
}
$this->set(compact('user'));
}
I read in the book from cakephp but I can't get it working, nor wrap my head around it very well.
Can somebody see or explain the mistake I'm making here?
If you need further information please tell me.

The Problem was, that the whole skelleton was baked, and I didn't follow the naming conventions for this particular join-table.
From CakePHP - Conventions
Join tables, used in BelongsToMany relationships between models, should be named after the model tables they will join or the bake command won’t work, arranged in alphabetical order (articles_tags rather than tags_articles). If you need to add additional columns on the junction table you should create a separate entity/table class for that table.
Therefore my Relations were defined wrong. I had to delete the MVC for UsersRoles, rename my table from users_roles to roles_users and bake it again.
There is another way to handle that, and use the "wrongly" named table, but I decided to change the database according to the conventions since I followed them on every other part of my application.
BUT! I will reconfigure this scenario in near future and fiddle a way to resolve this problem with the custom named join-table.

Related

Setting a default value on join table from another table when model is edited or created

Hoping someone may be able to point me in the right direction.
I have a app that consists of (among other things) Recommendations and Assessments. They are joined with a join table that includes extra fields that I would like to update but am struggling to figure out how.
As you can see above, when I create a Reccommendation, I set the following fields:
default_user_impact
default_business_impact
default_deployment_complexity
default_criticality
Now when I create a new Assessment or edit one that has not got any Recommendations linked the Assessment saves fine because nothing is needing to be written to the join table.
When I try to edit an Assessment to include one or more Recommendations, the app tries to write the link to the join table and fails because the user_impact, business_impact, deployment_complexity and criticality fields aren't specified - perfectly normal because I have set the fields to required in MySQL right? The error I get in CakePHP is
SQLSTATE[HY000]: General error: 1364 Field 'user_impact' doesn't have a default value
What I want to be able to do is at the time of editing or creating an Assessment is to use the values in the Recommendations table to populate the corresponding join table entries. Any ideas how to go about this?
So as an example:
user_impact = default_user_impact
business_impact = default_business_impact
deployment_complexity = default_deployment_complexity
criticality = default_criticality
The reason I want to do this is so that I can have the Recommendations set with values for those fields, and then if a user wants to run an assessment and they want to adjust the values just for their own assessment then it won't impact others etc.
Here is my AssessmentsTable association.
$this->belongsToMany('Recommendations', [
'foreignKey' => 'assessment_id',
'targetForeignKey' => 'recommendation_id',
'joinTable' => 'assessments_recommendations',
'through' => 'assessments_recommendations',
]);
Here is my RecommendationsTable association.
$this->belongsToMany('Assessments', [
'foreignKey' => 'recommendation_id',
'targetForeignKey' => 'assessment_id',
'joinTable' => 'assessments_recommendations',
'through' => 'assessments_recommendations',
]);
Here is my AssessmentsRecommendations association:
$this->belongsTo('Assessments', [
'foreignKey' => 'assessment_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Recommendations', [
'foreignKey' => 'recommendation_id',
'joinType' => 'INNER',
]);
This is what my AssessmentsController edit function looks like:
public function edit($id = null)
{
$assessment = $this->Assessments->get($id, [
'contain' => ['Recommendations'],
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$assessment = $this->Assessments->patchEntity($assessment, $this->request->getData(), ['associated'=>['Recommendations._joinData']]);
if ($this->Assessments->save($assessment, ['associated' => ['Recommendations._joinData']])) {
$this->Flash->success(__('The assessment has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The assessment could not be saved. Please, try again.'));
}
$clients = $this->Assessments->Clients->find('list', ['limit' => 200]);
$recommendations = $this->Assessments->Recommendations->find('list', ['limit' => 200]);
$this->set(compact('assessment', 'clients', 'recommendations'));
}
Now when I've added the beforeSave function to the AssessmentsRecommendationsTable I see the following error:
Argument 2 passed to App\Model\Table\AssessmentsRecommendationsTable::beforeSave() must be an instance of App\Model\Table\EntityInterface, instance of Cake\ORM\Entity given, called in /var/www/html/csa-portal/vendor/cakephp/cakephp/src/Event/EventManager.php on line 310
Any help would be much appreciated.
First, the associations you are using are wrong. It should be like this
For AssessmentsTable
$this->hasMany('AssessmentsRecommendations', [
'foreignKey' => 'assessment_id'
]);
For RecommendationsTable
$this->hasMany('AssessmentsRecommendations', [
'foreignKey' => 'recommendation_id'
]);
For AssessmentsRecommendationsTable
$this->belongsTo('Assessments', [
'foreignKey' => 'assessment_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Recommendations', [
'foreignKey' => 'recommendation_id',
'joinType' => 'INNER',
]);
Now for the default values, you have to user beforeSave in you AssessmentsRecommendationsTable.php file.You can modify your data as per your need here before the save.
public function beforeSave(Event $event, EntityInterface $entity, \ArrayObject $options)
{
if ($entity->isNew()) { // Returns true when you add new record
$recommendation = TableRegistry::getTableLocator()->get('Recommendations')->get($entity->recommendation_id);
$entity->user_impact = $recommendation->default_user_impact;
$entity->business_impact = $recommendation->default_business_impact;
$entity->deployment_complexity = $recommendation->default_deployment_complexity;
$entity->criticality = $recommendation->default_criticality;
}
}
I have never used belongsToMany, if the associations works for you then ignore the association part.
Have you considered writing a Rule to handle this?
https://book.cakephp.org/3/en/orm/validation.html#applying-application-rules

CakePHP 3 - saving associated data with correct id

I am fairly new to CakePHP, and am working on an event calendar. The events can be single day events or multiple days, so I created an events table and a dates table. When I save an event, the dates are saved in the Dates table as expected BUT the event_id field is always saved as 0 instead of the actual event id.
I think the relationships between the Events and Dates table is set properly.
This is what I have for the DatesTable:
$this->table('dates');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Events', [
'foreignKey' => 'event_id'
]);
This is my EventsTable:
$this->table('events');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Dates', [
'foreignKey' => 'event_id',
'joinType' => 'INNER'
]);
This is the code in my EventsController (WetKit is one of our plugins):
$event = $this->Events->newEntity();
if ($this->request->is('post')) {
$event = $this->Events->patchEntity($event, $this->request->data, [
'associated' => 'dates'
]);
if ($this->Events->save($event)) {
$this->Flash->success(__('The form has been saved.'));
} else
$this->WetKit->flashError(__('The form could not be saved because error(s) were found.'), $event);
}
How can I get the event id to save in the Dates table?
The association on the EventsTable side is wrong, it should be either a hasMany (1:n) association, or a hasOne (1:1) association. Given that you're talking about multiple days, I'd assume that you're looking for the former.
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together

CakePHP 3: Retrieving deep associations

My model has
Conversations - hasMany - Messages
Conversations - hasMany - ConversationsRecipients
ConversationsRecipients - belongTo - Users or Applicants (depending on the flag set by field recipient_type. If recipient_type is A then it means Applicants)
So when I try to retrieve conversations for a particular Applicant, I use the following code
$conversationsTable = TableRegistry::get('Conversations');
$conversations = $conversationsTable->find()
->join([
'ConversationsRecipients' => [
'table' => 'conversations_recipients',
'type' => 'inner',
'conditions' => ['recipient_id' => $id, 'recipient_type' => 'A']
]
])
->contain([
'Messages.Users' => function ($q) {
return $q
->select(['Users.username'])
->contain(['UsersProfiles']);
},
'Messages.Applicants' => function($q) {
return $q
->select(['Applicants.firstname', 'Applicants.lastname']);
}
])
->all();
return $conversations;
This works fine - except for one part - but it doesn't retrieve the deeply contained model - UsersProfiles. Am I missing something?
Try this:
return $q
->select(['Users.username'])
->autoFields(true)
->contain(['UsersProfiles']);
When you include a select in your query, that's all that Cake will include, unless you include the autoFields call.

CakePHP HABTM Association not working

I've been trying to find an answer to my problem for hours. I am currently working with cakePHP 2.4.
I have two models, Users and Groups. I have created the following associations for each:
(User.php)
public $hasAndBelongsToMany = array(
'Group' =>
array(
'className' => 'Group',
'joinTable' => 'groups_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'group_id',
'unique' => true,
)
);
and (Group.php):
public $hasAndBelongsToMany = array(
'GroupUser' =>
array(
'className' => 'User',
'joinTable' => 'groups_users',
'foreignKey' => 'group_id',
'associationForeignKey' => 'user_id',
'unique' => true,
)
);
The reason I use GroupUser and not "User" is I get an error because I have already used "User for some other relation.
My form looks like this:
echo $this->Form->create('Group', array('controller' => 'group','action' => 'add'));
echo $this->Form->input('User.id', array('type' => 'hidden', 'value' => $authUser['id']);
echo $this->Form->input('Group.address');
echo $this->Form->end(__('Save', true));
I also have a table called groups_users with "id", "user_id" and "group_id"
When I submit the form, it created the new Group and saves the data, but the association is not created.
I tried manually filling a groups_users record with an existing user_id and group_id but still, when I use find(All), it doesn't find the expected association like it should according to the books.
I debugged the array that is being saved and it looks like this:
Array
(
[User] => Array
(
[user_id] => 39
)
[Group] => Array
(
[address] => asdasd, San Antonio, Texas 78233, EE. UU.
)
)
This is the code in my GroupsController add function:
if ($this->Group->save($this->request->data)) {
// redirect or do something
}
I have tried changing the array to work with saveAll like in the books, still only created new record but no association. And as I said, I mannually created a record and tried finding it and it wouldn't find it anyway.
I solved it apparently, I think it was because I hadn't created the GroupsUser.php model file. Not rreally sure because I changed a bunch of stuff! But maybe it helps someone.

CakePHP- querying a HABTM table and then accessing data

CAKEPHP question
I am querying my HABTM table successfully and returning the id of every student with the given group_id. This is my code for this.
$students = $this->GroupsStudents->find('list', array('conditions' => array('group_id' => $id)));
It works, no problem. I need to somehow use this info (namely the student's id), which is stored in $students, to query my students table and extract student's names based on their id.
If someone could give me some insight on this, that would be greatly appreciated.
if i'm understanding you right. as you can see from this if you have the id you can easily get the students name even though i'm not sure why you would do this and not just foreach the name.
foreach ($students as $id => $name) {
echo $students[$id]; // produces name
}
In Student model define relation with GroupStudent model as shown below:
var $hasMany = array(
'GroupStudent' => array(
'className' => 'GroupStudent',
'foreignKey' => 'student_id'
)
);
Then your write your query as
$students = $this->Student->find('all',
array(
'conditions' => array('GroupStudent.group_id' => $id)
'fields'=>array('Student.name','Student.id','GroupStudent.group_id'))
);
Note: Make sure your controller has $uses=>array('Student','GroupStudent'); defined!
and your are using plural names for model GroupStudents so correct them if possible