我可以使用相同的 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 种用户类型是:
- 战斗机
- 裁判
- 经理
因此 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.php
和 ManagersTable.php
与 FightersTable
类似,但有自己的验证规则,并且可能有自己的特殊实体虚拟属性等等。
问题: 是否有更简单的方法来构建它,或者更具体地说,有另一种方法来执行 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
逻辑提取到 RoleBehavior
或 MultiRoleBehavior
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'],
]);
希望对您有所帮助。
我有一个 my_users
table,我现在需要通过删除 role
列来重构它,以支持每个用户支持多个角色。例如,我正在处理的 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.php
和 ManagersTable.php
与 FightersTable
类似,但有自己的验证规则,并且可能有自己的特殊实体虚拟属性等等。
问题: 是否有更简单的方法来构建它,或者更具体地说,有另一种方法来执行 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
逻辑提取到 RoleBehavior
或 MultiRoleBehavior
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'],
]);
希望对您有所帮助。