首页 > 解决方案 > Laravel 使用 BelongsToMany 中关联的类别过滤用户

问题描述

在我们的应用程序中,每个类别和用户都可以关联在一起。

每个类别可以关联一个或多个用户,每个用户可以关联一个或多个类别

例如你假设我们有这个类别结构:

application
     web
         php
            laravel
            lumen
         web_design
            html
            css
            js 
     mobile    
         java
         flutter

在这种结构中,每个类别都可以有一个或多个孩子,我们parent_id在数据库结构中使用它们来实现它们。

现在,这些类别中的每一个或某些类别都与一个或多个用户相关联,例如:

application (Alfred)
     web (Alfred)
         php (Alfred)
            laravel (Alfred)
            lumen   (Alfred)
         web_design (Alfred)
            html (Alfred)
            css  (Alfred)
            js   (Alfred)
     mobile  (Alfred)   
         java (Alfred)
         flutter (Alfred)

此结构中与该用户关联的所有类别:(Alfred)并且每个类别都可以与另一个用户关联,例如:

application (Alfred)
     web (Alfred)
         php (Alfred,Ella)
            laravel (Alfred,Ella)
            lumen   (Alfred,Ella)
         web_design (Alfred,Elizabeth)
            html (Alfred,Elizabeth)
            css  (Alfred,Elizabeth)
            js   (Alfred,Elizabeth)
     mobile  (Alfred,Jack)   
         java (Alfred,Jack)
         flutter (Alfred,Jack)

或者换句话说,你可以假设我们有这样的结构:

application (Alfred)
     web (Alfred)
         php (Alfred, Ella)
            laravel (Alfred, Ella)
            lumen   (Alfred,Ella ,Linda)
         web_design (Alfred, Elizabeth)
            html (Alfred,Elizabeth)
            css  (Alfred,Elizabeth)
            js   (Alfred,Elizabeth, Scarlett)
     mobile  (Alfred, Jack)   
         java (Alfred, Jack)
         flutter (Alfred, Jack, Jim)

此用户BelongsToManyRole我们同步到数据库中

  1. Alfred : is-portal-manager
  2. Ella : is-manager
  3. 伊丽莎白:是经理
  4. 杰克:是编辑
  5. 斯嘉丽:是作家
  6. 吉姆:是作家

我们使用category_user表来同步这个结构来关联类别和用户

那么我们有五个表:users,,categoriesroles中间表:category_userrole_user

migrations数据库和表:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->nullable()->constrained();
    $table->string('name');
    $table->string('family');
    $table->string('username');
    //...
});

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('parent_id')->nullable();
    $table->string('name');
    //...
});

Schema::create('roles', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('label');
    $table->timestamps();
});

Schema::create('category_user', function (Blueprint $table) {
    $table->foreignId('category_id')->constrained()->onDelete('cascade');
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->primary(['category_id', 'user_id']);
});

Schema::create('role_user', function (Blueprint $table) {
    $table->foreignId('role_id')->constrained()->onDelete('cascade');
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->primary(['role_id', 'user_id']);
});

最后,我们的问题是什么

我们想要获取与category_user表关联和定义的登录用户的类别,这意味着当Alfred登录系统时应该具有与之关联的所有类别。(一个或多个类别),另一个用户应该具有相同的策略,

  1. Alfred : is-portal-manager (所有类别都有子类别: application,web,php,laravel,lumen,web_design,html,css,js,mobile,java,flutter)
  2. Ella : is-manager (php,laravel,lumen)
  3. 伊丽莎白 : is-manager (web_design,html,css,js)
  4. Jack : is-editor (mobile,java,flutter)
  5. 斯嘉丽:是作家(js)
  6. 吉姆:是作家(颤抖)

完整的结构

application (Alfred: is-portal-manager)
     web (Alfred: is-portal-manager)
         php (Alfred: is-portal-manager, Ella: is-manager)
            laravel (Alfred: is-portal-manager, Ella: is-manager)
            lumen   (Alfred: is-portal-manager,Ella: is-manager ,Linda)
         web_design (Alfred: is-portal-manager, Elizabeth: is-manager)
            html (Alfred: is-portal-manager,Elizabeth: is-manager)
            css  (Alfred: is-portal-manager,Elizabeth: is-manager)
            js   (Alfred: is-portal-manager,Elizabeth: is-manager, Scarlett: is-writer)
     mobile  (Alfred: is-portal-manager, Jack: is-editor)   
         java (Alfred: is-portal-manager, Jack: is-editor)
         flutter (Alfred: is-portal-manager, Jack: is-editor, Jim: is-writer)

楷模:

class Category extends Model
{
    public function parent(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'parent_id', 'id', 'parent');
    }

    public function subcategories(): HasMany
    {
        return $this->hasMany(Category::class, 'parent_id', 'id');
    }

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
}

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
}

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Get the parent User to which the current User belongs.
     */
    public function parent(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Get all Users which belong to the current User.
     */
    public function kids(): HasMany
    {
        return $this->hasMany(User::class);
    }

    public function categories(): BelongsToMany
    {
        $categories = $this->belongsToMany(Category::class);
        return $categories;
    }

    /**
     * Determine whether the current user has prime Role of Portal Manager.
     */
    public function isPortalManager(): Boolean
    {
        return $this->roles->contains('label', 'is-portal-manager');
    }

    /**
     * Determine whether the current user has prime Role of Manager.
     */
    public function isManager(): Boolean
    {
        return $this->roles->contains('label', 'is-manager');
    }

    /**
     * Determine whether the current user has prime Role of Editor.
     */
    public function isEditor()
    {
        return $this->roles->contains('label', 'is-editor');
    }

    /**
     * Determine whether the current user has prime Role of Writer.
     */
    public function isWriter()
    {
        return $this->roles->contains('label', 'is-writer');
    }

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }

    /**
     * Determine whether current User has the given role.
     * Given role can be a Role object or string or int
     *
     * @param Role|string|int $role
     * @return boolean
     */
    public function hasRole($role)
    {
        //dd('a');
        /** When $role is an object of class Role */
        if ($role instanceof Role) {
            return !!$role->intersect($this->roles)->count();
        }
        /** When $role is an integer */
        if (is_int(($role))) {
            return $this->roles->contains('id', $role);
        }
        /**
         * When $role is string
         *  - Check against id (in case id is uuid stored as string)
         *  - Check against name
         *  - Check against label
         */
        if (is_string($role)) {
            return !!(
                $this->roles->contains('id', $role) ||
                $this->roles->contains('name', $role) ||
                $this->roles->contains('label', $role)
            );
        }
    }

    public function hasRoleByName($role)
    {
        if ($role == null) {
            return false;
        }
        if (is_string($role)) {
            return $this->roles->contains('name', $role) || $this->roles->contains('label', $role);
        } else {
            return !!$role->intersect($this->roles)->count();
        }
    }
}

我们希望使用以下代码为每个用户获取所有类别(一个或多个):

$user = User::find(1);
return $user->categoires();

标签: phplaravelrelationship

解决方案


您可以尝试以下特征


namespace App\Concerns;

use App\Models\Category;
use Illuminate\Database\Eloquent\Collection;

trait HasCategories
{
    /**
     * Get all Categories associated with the User in nested tree structure
     */
    public function availableCategories(): Collection
    {
        $categories = $this->categories;

        $parents = $categories->filter(fn($cat)  =>
           !in_array($cat->parent_id, $categories->pluck('id')->all()) || is_null($cat->parent_id)
        );

        $parents->map(fn($parent) => $this->setNested($parent, $categories));

        return $parents;
    }

    /**
     * Set the nested structure for the given $parent with relation.
     */
    protected function setNested($parent, $categories)
    {
        $parent->setRelation('subcategories', $categories->where('parent_id', $parent->id));
        $parent->subcategories->map(function($sub) use($categories){
            if($categories->contains('parent_id', $sub->id)) {
                $this->setNested($sub, $categories);
            }
            return $sub;
        });

        return $parent;
    }
}

推荐阅读