我可以使用相同的 table differently/in 更 Cake 的方式重构我的多个用户类型的设置吗?

Can I refactor my setup of multiple user types using the same table differently/in a more Cake way?

我有一个 my_users table,我现在需要通过删除 role 列来重构它,以支持每个用户支持多个角色。例如,我正在处理的 3 种用户类型是:

  1. 战斗机
  2. 裁判
  3. 经理

因此 table 设置如下所示:

Users.php

use CakeDC\Users\Model\Table\UsersTable as BaseUsersTable;

class UsersTable extends BaseUsersTable
{
    public function initialize(array $config)
    {   
        parent::initialize($config);

        $this->setEntityClass('Users\Model\Entity\User');

        $this->belongsToMany('UserRoles', [
            'through' => 'users_user_roles'
        ]); 

        $this->hasMany('UsersUserRoles', [
            'className' => 'Users.UsersUserRoles',
            'foreignKey' => 'user_id',
            'saveStrategy' => 'replace',
        ]);
    }

    public function findRole(Query $query, array $options)
    {
        return $query
            ->innerJoinWith('UserRoles', function($q) use ($options) {
                return $q->where(['role_key' => $options['role_key']]);
            });
    }
}

MyUsersTable.php

class MyUsersTable extends Table
{   
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('my_users');

        $this->setPrimaryKey('user_id');

        $this->belongsTo('Users', [
            'className' => 'Users.Users',
            'foreignKey' => 'user_id',
        ]);
    }

    /**
     * @param Cake\Event\Event $event
     * @param Cake\ORM\Query $query
     * @param ArrayObject $options
     */
    public function beforeFind(Event $event, Query $query, \ArrayObject $options)
{
        // set the role
        if (defined(static::class.'::ROLE') && mb_strlen(static::ROLE) > 0) {
            $role = static::ROLE;

            // set user conditions
            $query->innerJoinWith('Users', function($query) use ($role) {
                return $query->find('role', [
                    'role_key' => $role,
                ]);
            });
        }
    }

    /**
     * @param \Cake\ORM\Query $query
     * @param array $options
     */
    public function findByRegistrationCode(Query $query, array $options): Query
    {
        $query->where([
            $this->aliasField('registration_no') => $options['registration_no']
        ]);

        return $query;
    }
}

FightersTable.php

use MyUsers\Model\Table\MyUsers;

class FightersTable extends MyUsersTable
{       
    const ROLE = 'fighter';

    public function initialize(array $config)
    {   
        parent::initialize($config);

        $this->setEntityClass('Fighters\Model\Entity\Fighter');
    }       

    /**
     * @param Validator $validator
     * @return Validator $validator
     */
    public function validationDefault(Validator $validator): Validator
    {
        $validator = parent::validationDefault($validator);

        $validator->allowEmpty('field');
    }
}

RefereesTable.phpManagersTable.phpFightersTable 类似,但有自己的验证规则,并且可能有自己的特殊实体虚拟属性等等。

问题: 是否有更简单的方法来构建它,或者更具体地说,有另一种方法来执行 beforeFind 来区分角色?如果 role 对用户的要求保持 1:1,我可能会做这样的事情:

$this->belongsTo('Fighters', [
    'conditions' => [
        'role' => 'fighter'
    ],
    'foreignKey' => 'user_id',
    'className' => 'MyUsers',
]);

我将不胜感激任何有关重组的见解。

我不知道我是否理解了这些想法,但也许这段代码会有所帮助。它使用类似命名空间别名的东西。

FightersTable.php 文件:

namespace App\Model\Table;

use Cake\ORM\Query;
use CakeDC\Users\Model\Table\UsersTable as FighterUsersTable;

class FightersTable extends FighterUsersTable
{
    // Yous callbacks (like beforeFind)
    ...
}

现在您可以访问 CakeDC/Users table:)

在您有多个角色的情况下,我通常 done/seen 使用一个 table 来存储角色(我们称之为 UserRoles),并使用另一个查找 table (让我们称之为 UserRoleAssignments)将用户连接到特定角色。因此,例如您的 myUsersTable 将包含与 UserRoleAssignments 的 hasMany 关系。这将消除对角色字段以及每个角色的单独 table 的需要。您的 findRole() 方法看起来像这样:

public function findRole(Query $query, array $options)
{
    return $query
        ->innerJoinWith('UserRoleAssignments', function($q) use ($options) {
            return $q->where(['user_id' => $options['user_id']]);
        });
}

如果您只希望用户具有一个角色,则可以将 first() 调用链接到此;但是,这种设置很好,因为如果一个用户可以拥有多个角色,那么您可以简单地链接一个 toArray() 调用和 return 属于该用户的一组角色。

您可以定义 belongsTo 关联以使用 findByRole finder:

 $this->belongsTo('Fighters', [
    'foreignKey' => 'user_id',
    'className' => 'MyUsers',
    'finder' => ['byRole' => ['role' => 'fighter']]
]);

当然,您必须在 MyUsers:

中定义查找器
public function findByRole(Query $query, \ArrayObject $options)
{
    $role = $options['role'];

    // set user conditions
    $query->innerJoinWith('Users', function($query) use ($role) {
       return $query->find('role', [
          'role_key' => $role,
           ]);
        });
    }
}

我还会将 beforeFind 逻辑提取到 RoleBehaviorMultiRoleBehavior behavior 中,即: src/Model/Behavior/RoleBehavior.php:

<?php
namespace App\Model\Behavior;

use Cake\ORM\Behavior;
use Cake\ORM\Query;
use Cake\Event\Event;

/**
 * Role-specific behavior
 */
class RoleBehavior extends Behavior
{

    /**
     * @var array multiple roles support
     */
    protected $_defaultConfig = [
        'roles' => []
    ];


    public function initialize(array $config)
    {
        parent::initialize($config);
        if (isset($config['roles'])) {
            $this->config('roles', $config['roles'], false /* override, not merge*/);
        }
    }

    public function beforeFind(Event $event, Query $query, \ArrayObject $options, $primary) {
        // set user conditions
        $query->innerJoinWith('Users', function($query) use ($roles) {
            return $query->find('role', [
                'role_key' => $roles,
            ]);
        });
    }
}

它使用多个角色,但如果需要,您可以简单地将其更改为单个角色。这里还有一些其他要做的事情——可能将 $query->innerJoinWith('Users'... 提取到行为中的一个单独的方法中,然后在 MyUsers::findByRole(...)...

中调用它

接下来,您可以将此行为直接附加到扩展 类,只需将静态 ROLE 替换为行为配置:

use MyUsers\Model\Table\MyUsers;

class FightersTable extends MyUsersTable
{       
    public function initialize(array $config)
    {   
        parent::initialize($config);

        $this->setEntityClass('Fighters\Model\Entity\Fighter');

        $this->addBehavior('Role', [ 'roles' => ['fighter']]);
    }
}

或者您可以通过将行为附加到 MyUsers table(即来自控制器)来管理特定于角色的逻辑:

$this->MyUsers->addBehavior('Role', ['roles' => ['manager']])

You also can change the behavior setup on the fly:

$this->MyUsers->behaviors()->get('Role')->config([
   'roles' => ['manager'], 
]);

希望对您有所帮助。