我可以用不同的方式/以更蛋糕的方式重构我的多个用户类型的设置吗?

Posted

技术标签:

【中文标题】我可以用不同的方式/以更蛋糕的方式重构我的多个用户类型的设置吗?【英文标题】:Can I refactor my setup of multiple user types using the same table differently/in a more Cake way? 【发布时间】:2018-04-12 14:23:00 【问题描述】:

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

    战斗机 裁判 经理

所以表格设置如下所示:

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

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

【问题讨论】:

我看不出用户类型表对象需要扩展用户表对象的原因吗?如果只是验证,您可以创建不同的验证规则集并定义使用哪一个。因此,用户有一个 FighterProfiles、RefeeeeProfiless、AdminProfiles + 一个一套规则,每个都应该完成这项工作。 book.cakephp.org/3.0/en/orm/… 另一个原因是实体可以共享相似的方法。例如,如果我想要一个虚拟的 _getFullName 属性,我可以在 MyUserEntity 中指定它,其他人将拥有它。更不用说任何其他常用方法,例如搜索现有的“组织 ID#”,所有这些方法都将具有且不能重复。 使用特征、继承还是行为?或者这个github.com/UseMuffin/Sti 另外,如果每种类型的用户都有一个 org id,只需将该 id 移动到 users 表? 我已经在使用我指定的设置了。抱歉,如果这令人困惑,但我想知道问题 #1-3,其中包括我是否可以避免“陷阱”。就用户表而言,如果/当有人从 CakeDC/users 迁移/更新模式时,我想保持它的纯净。 只是将用户表复制到应用级迁移?我从来都不喜欢将这些类型的迁移保留在插件中,因为 users 表很可能会得到一些插件无法覆盖的额外内容。如果您不想将整个迁移移到该表,您仍然可以将应用级迁移中的单个字段添加到该表中。 【参考方案1】:

你可以定义belongsTo关联使用findByRolefinder:

 $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-&gt;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 表(即来自控制器)来管理您的角色特定逻辑:

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

You also can change the behavior setup on the fly:

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

希望对你有帮助。

【讨论】:

很棒的答案,正是我想要的。我主要将模型拆分为类,因为具有单独实体的灵活性和更容易自定义验证。如果您有自引用关联,您是否知道是否可以定义实体? 当你在finder中有查询,你可以调用它 $query->map(function($entity) return $entity->toSpecificEntity(); ); 或者,可能更简单: $query->map(function($entity) return new OtherEntity($entity->toArray());) 谢谢 - 完全忘记点击赏金按钮 :) @mederomuraliev 这是个好问题。这可能是我第一次面临如何以框架的方式完成某事的问题(坦率地说,b/c 甚至在询问之前你已经有了工作解决方案)。我觉得我需要学习如何为我刚开始使用的框架提出这样的问题......这就是我打算学习做的事情。我很高兴回答这个问题。谢谢!【参考方案2】:

在您拥有多个角色的情况下,我通常所做/看到的是使用一个表来存储角色(我们称之为 UserRoles),并使用另一个查找表(我们称之为 UserRoleAssignments)将用户连接到具体作用。因此,例如,您的 myUsersTable 将包含与 UserRoleAssignments 的 hasMany 关系。这将消除对角色字段以及每个角色的单独表的需要。您的 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() 调用并返回属于该用户的角色数组。

【讨论】:

我目前已经在做这样的事情,但是我使用 beforeFind 和多个表的模型设置呢?有没有其他选择? 我不明白。在您发布的代码中,您没有做这样的事情。如果您按照我的建议为您的数据库建模,则无需执行 beforeFind() 调用 我创建这些用户的方式不同,每个用户都有不同的必填字段和一个单独的进程,因此我将它们拆分为每个用户类型的单独插件以处理模板更改。我仍然需要一种方法来根据角色“设置”要使用的验证过程的类型,所以我需要在某个时候为用户定义角色。我认为拆分模型在这方面更容易,我应该将其添加到问题中。【参考方案3】:

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

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 表了:)

【讨论】:

Err - 我已经有了这个设置(虽然我的主要用户扩展了 CakeDC 用户),我只是想知道是否有更好的方法可以重组我目前拥有的东西。

以上是关于我可以用不同的方式/以更蛋糕的方式重构我的多个用户类型的设置吗?的主要内容,如果未能解决你的问题,请参考以下文章

UIWebView 以更有效的方式降低加载时​​间

是否可以在单个变量(批处理文件)中分配多个值?如果没有,有没有办法以更有效的方式运行它?

是否可以以更简单的方式输出包含DB值的下拉列表?

在 Flutter 和 Dart 中重构小部件的最优雅/高效的方式

如何以更简洁的方式为 discord.py 编写嵌入代码?

更简洁优雅的 VARIANT 类型使用方式