I am creating a tool that allows to generate dynamic forms. There are several tables in question:
Form [the Form master table]
FormField [JoinTable to Field]
Field [the fields available for inclusion in Form]
FieldValidation [The table containing relation data between the FormField and the Validation option]
Validation [The available Validation options]
For the FieldValidation - this could in effect be a hasMany from Field, but I am unsure of whether I need to set up this relation from the Field table, or from the join table FieldValidation. The Validation table literally just includes the definitions for the validation options. This does not actually need to be a belongsToMany relation from the FormField/Field table. A hasMany is fine if that simplifies things.
Is this even possible?
Form -> [FormField] -> Field -> [FieldValidation] -> Validation
I have never done this before - so if there is a better way to approach this, I am all ears. My main concern is being able to select Form, contain Field's, and then contain the Validation for each field selected. Obviously, multiple validation rules can be selected per field.
A little late, but I did resolve this issue.
ERD Diagram of Physical Relationship in DB
Model: UsersTable
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('users');
$this->displayField('username');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Projects', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'project_id',
'through' => 'ProjectsUsers'
]);
$this->hasMany('ProjectsUsers', [
'foreignKey' => 'user_id'
]);
}
}
Model: ProjectsTable
class ProjectsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects');
$this->displayField('name');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Users', [
'foreignKey' => 'project_id',
'targetForeignKey' => 'user_id',
'through' => 'ProjectsUsers'
]);
$this->hasMany('ProjectsUsers', [
'foreignKey' => 'project_id'
]);
}
}
Model: ProjectsUsersTable - this is the model for the JOIN table (through)
class ProjectsUsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects_users');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id'
]);
$this->belongsTo('Projects', [
'foreignKey' => 'project_id'
]);
$this->hasMany('ProjectsUsersPermissions', [
'foreignKey' => 'projects_users_id'
]);
}
}
Model: ProjectsUsersPermissions - this is the relation to the join table
class ProjectsUsersPermissionsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects_users_permissions');
$this->displayField('role');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('ProjectsUsers', [
'foreignKey' => 'projects_users_id'
]);
}
}
Then the controller find action
$this->Projects->find()
->where(
[
'Projects.id' => $projectId
]
)
->contain(
[
'Users', // through belongsToMany
'ProjectsUsers' => [ // through hasMany [joinTableModel]
'ProjectsUsersPermissions' // through hasMany
]
]
)
->first();
This may be overkill for this scenario, and it is not my exact implementation - so don't think I am just doing unnecessary joins/contains. In my real life scenario, this works perfectly.
Hope this helps someone!
Related
I am using CakeDC Users and as part of the registration I would like the registrant to choose some checkbox values that correspond to another Model in the application. I am able to get the form to load the choices just fine, but I am unable to get it to save to the join table even though I have added all of the associations and accessibilities where it would seem relevant. The form is displaying in a similar way to other areas where I am saving the same type of association with a different model.
TypesTable.php
{
public function initialize(array $config): void
{
//$this->addBehavior('Timestamp');
$this->setDisplayField('type');
$this->setPrimaryKey('id');
$this->belongsToMany('Words');
$this->belongsToMany('Users');
$this->belongsTo('Languages', [
'foreignKey' => 'language_id',
'joinType' => 'INNER',
]);
}```
UsersTable.php (in the plugin folders)
```class UsersTable extends Table
{
...
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('username');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->addBehavior('CakeDC/Users.Register');
$this->addBehavior('CakeDC/Users.Password');
$this->addBehavior('CakeDC/Users.Social');
$this->addBehavior('CakeDC/Users.LinkSocial');
$this->addBehavior('CakeDC/Users.AuthFinder');
$this->hasMany('SocialAccounts', [
'foreignKey' => 'user_id',
'className' => 'CakeDC/Users.SocialAccounts',
]);
$this->hasMany('Types', [
'foreignKey' => 'user_id', 'targetForeignKey' => 'type_id',
'joinTable' => 'types_users']);
}```
In order for a "belongsToMany" association to work in this instance, both *Table.php files must have the relations listed as belongsToMany. Don't forget to make the field accessible in the Entities as well.
One also needs to place the associated array in the patchEntity function in the RegisterBehavior.php file.
UserTable.php (in CakeDC users)
public function initialize(array $config): void
{
...
$this->belongsToMany('Types', [
'foreignKey' => 'user_id', 'targetForeignKey' => 'type_id',
'joinTable' => 'types_users']);
}
TypesTable.php (in app)
class TypesTable extends Table
{
public function initialize(array $config): void
{
....
$this->belongsToMany('Users');
$this->belongsTo('Languages', [
'foreignKey' => 'language_id',
'joinType' => 'INNER',
]);
}
RegisterBehavior.php
$user = $this->_table->patchEntity(
$user,
$data,
['validate' => $validator ?: $this->getRegisterValidators($options), 'associated' => ['Types']]
);
I am learning having a problem insert records in Tables with association "hasOne", When I insert into the Table Users it should also insert in Table Customers but is only working with Users. I've looked people had similar problems but i really don't know what I am doing wrong. I appreciate any help
My User Model
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasOne('Customers', [
'foreignKey' => 'user_id'
]);
}
My Customer Model
class CustomersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('customers');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id'
]);
}
My request->getData()
[
'Users' => [
'Customers' => [
'first_name' => 'name',
'last_name' => 'test',
'gender' => 'Male',
'postcode' => '1234'
],
'username' => 'user1',
'password' => '12134'
],
'done' => '1'
]
My Action
public function addCustomer()
{
//Configure::write('debug',true);
// debug($this->request->getData());
$usersTable = TableRegistry::get('Users');
$user = $usersTable->newEntity();
if ($this->request->is('post')) {
$user = $usersTable->patchEntity($user,$this->request->getData(),['associated' => ['Customers']]);
if ($usersTable->save($user)) {
$this->Flash->success('The Customer has been saved.');
return $this->redirect(['action' => 'addCustomer']);
}
$this->Flash->error('Unable to add the Customer.');
}
}
I though that your User model can not have user_id as its foreinKey
I need to build the associations like Group hasMany users and User belongToMany groups.
But I can't get the right result, it always use the wrong table instead groups_relations
My models:
class GroupsTable extends Table
{
public function initialize(array $config)
{
$this->setTable('groups');
$this->setDisplayField('title');
$this->setPrimaryKey('id');
$this->hasMany('Users', [
'joinTable' => 'groups_relations',
'foreignKey' => 'user_id',
]);
}
}
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->table('user_users');
$this->belongsToMany('Groups', [
'joinTable' => 'groups_relations',
'foreignKey' => 'group_id',
]);
}
}
class GroupsRelationsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('groups_relations');
$this->setDisplayField('group_id');
$this->setPrimaryKey('id');
$this->belongsTo('Groups', [
'foreignKey' => 'group_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
}
}
And my table groups_relations:
id | group_id | user_id
I run query as:
$groupsWithUsers = $this->Groups->find('all', array(
'contain' => array('Users')
));
I can't understand how to tell to cake use my intermediary table and append reuslts to array.
joinTable is not a valid configuration key for a hasMany association. I think that you want to have Groups belongsToMany Users. Another clue about this is that hasMany is the "opposite" of belongsTo, while belongsToMany is it's own opposite. (That is, if A hasMany B, then B belongsTo A, but if A belongsToMany B, then B belongsToMany A.) Note that you will also want to change your GroupsRelations association with Users to belongsTo.
Is this code that was baked for you? Because it should know better. When I run into sticky association problems, I sometimes have Cake bake the model code for me, and then look at how the result differs from what I've written.
Rather than trying to use the relation the way you are doing why not just select from the relations table in the first place. This seems like the more Cake way of doing things. You can exclude the conditions clause if you want all data back.
$groupsWithUsers = $this->GroupsRelations->find('all', array(
'contain' => ['Users', 'Groups'],
'conditions' => ['Group.id' => $id]
)
);
After further looking into this I found something I have not used but seems to fit exactly what you need its a belongsToMany using an intermediary table. In your table file for the users you would add the following. A similar entry would be added to the group page.
$this->belongsToMany('Groups', [
'through' => 'GroupRelations',
]);
I'm trying to create a set of CRUDs using cakephp3. My database model looks like this:
I used the cake's tutorial on authentication to create the users table and it's classes, it's working fine. But I want to use a more complex set of roles, so I created these other tables. After creating the database model I baked the corresponding classes, made a few tweaks and got the systems and the roles CRUD's to work. Now I want to integrate the roles_users table, probably inside of user's CRUD.
I would like to see how cake's bake would do it before coding this relation myself, but I'm unable to open /rolesUsers. When I call the URL, I get the following error message:
Cannot match provided foreignKey for "Roles", got "(role_id)" but expected foreign key for "(id, system_id)" RuntimeException
I think it happens because system_id is a PK in roles table and isn't present in roles_users (I'll show the baked models and this PK will be present at roles class). Is there an easy way to make it work without adding system_id in roles_users? IMO adding this extra field wouldn't be a big problem, but I would like to know if I'm doing something wrong, some bad design decision.
My src/Model/Table/RolesUsersTable.php:
<?php
namespace App\Model\Table;
use App\Model\Entity\RolesUser;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* RolesUsers Model
*
* #property \Cake\ORM\Association\BelongsTo $Users
* #property \Cake\ORM\Association\BelongsTo $Roles
*/
class RolesUsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('roles_users');
$this->displayField('user_id');
$this->primaryKey(['user_id', 'role_id']);
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'joinType' => 'INNER'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('valido_ate', 'valid', ['rule' => 'date'])
->requirePresence('valido_ate', 'create')
->notEmpty('valido_ate');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
$rules->add($rules->existsIn(['role_id'], 'Roles'));
return $rules;
}
}
My src/Model/Table/RolesTable.php:
<?php
namespace App\Model\Table;
use App\Model\Entity\Role;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Roles Model
*
* #property \Cake\ORM\Association\BelongsTo $Systems
*/
class RolesTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('roles');
$this->displayField('name');
$this->primaryKey(['id', 'system_id']);
$this->belongsTo('Systems', [
'foreignKey' => 'system_id',
'joinType' => 'INNER'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('id', 'valid', ['rule' => 'numeric'])
->allowEmpty('id', 'create');
$validator
->requirePresence('name', 'create')
->notEmpty('name');
$validator
->add('status', 'valid', ['rule' => 'numeric'])
->requirePresence('status', 'create')
->notEmpty('status');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['system_id'], 'Systems'));
return $rules;
}
}
My src/Model/Table/UsersTable:
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table{
public function validationDefault(Validator $validator){
return $validator
->notEmpty('username', 'O campo nome de usuário é obrigatório')
->notEmpty('password', 'O campo senha é obrigatório')
->notEmpty('role', 'O campo perfil é obrigatório')
->add('role', 'inList', [
'rule' => ['inList', ['admin', 'author']],
'message' => 'Escolha um perfil válido'
]
);
}
}
?>
Answered by user jose_zap in #cakephp #freenode:
In RolesUsersTable.php, initialize function, I added a parameter to both $this->belongsTo calls, including the 'bindingKey' and value 'id'. So this old code:
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'joinType' => 'INNER'
]);
became this:
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'bindingKey' => 'id',
'joinType' => 'INNER'
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'bindingKey' => 'id',
'joinType' => 'INNER'
]);
I'm trying to save an Order entity, but patchEntity always set two fields that are foreign keys to null.
Orders are associated to Addresses with 2 associations (Delivery and Invoice).
Associated addresses already exists, so I just want to save address id as a foreign key into Orders table.
OrdersTable
namespace OrderManager\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use OrderManager\Model\Entity\Order;
/**
* Orders Model
*/
class OrdersTable extends Table {
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config) {
$this->table('orders');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Contacts', [
'foreignKey' => 'contact_id',
'joinType' => 'INNER',
'className' => 'ContactManager.Contacts'
]);
// ...
$this->belongsTo('DeliveryAddresses', [
'foreignKey' => 'delivery_address',
'className' => 'ContactManager.Addresses'
]);
$this->belongsTo('InvoiceAddresses', [
'foreignKey' => 'invoice_address',
'className' => 'ContactManager.Addresses'
]);
}
public function validationDefault(Validator $validator) {
// ...
$validator
->add('delivery_address', 'valid', ['rule' => 'numeric'])
->allowEmpty('delivery_address');
$validator
->add('invoice_address', 'valid', ['rule' => 'numeric'])
->allowEmpty('invoice_address');
// ...
}
Controller
$data = [
// ...
'contact_id' => 34,
'delivery_address' => 8,
'invoice_address' => 8,
'currency' => 'Euro',
'total_paid' => '100.00',
'shipping_number' => ''
// ...
];
$entity = $this->Orders->newEntity();
$entity = $this->Orders->patchEntity($entity, $data);
debug($entity);
debug($entity) always tells me :
'delivery_address' => null,
'invoice_address' => null,
When I remove the belongsTo associations (DeliveryAddresses and InvoiceAddresses), my fields get the numeric value (8). But I need these associations.
How can I keep these associations and save numeric values for the foreign keys ?
The foreign key names are conflicting with the association property names (where the data for the associations is being stored), which are by default being derived from the association name, and in case of a belongsTo one it's the singular underscored variant of the association name, ie delivery_address and invoice_address.
See Cookbook > Database Access & ORM > Associations > BelongsTo Associations
To fix this, either stick to the conventions and append _id to your foreign keys, ie delivery_address_id and invoice_address_id, or change the property names using the propertyName option
$this->belongsTo('DeliveryAddresses', [
'propertyName' => 'delivery_address_data',
//...
]);
$this->belongsTo('InvoiceAddresses', [
'propertyName' => 'invoice_address_data',
//...
]);
Unless you're working with a legacy database, I'd strongly recommend to choose the former solution and make your foreign keys stick to the conventions!