Search code examples
phpmysqlcakephpcrudcakephp-bake

How can I correctly relate these tables in cakephp?


I'm trying to create a set of CRUDs using cakephp3. My database model looks like this:

My ER model

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

    }
}
?>

Solution

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