不使用第三方包的情況下,如何在 Laravel 中建立一套優雅的使用者許可權管理。

medz發表於2017-08-25

需求場景

就是使用者組+許可權節點,這個需求 laravel 有很多很好的第三方包實現。下面描述程式碼不參與快取機制純資料庫查詢,給大家提供一個思路。

下面的程式碼都是來自於 ThinkSNS+ ,是基於 Laravel 全新開發的 ThinkSNS 社交開源專案,遵循 Apache-2.0 開源協議。歡迎 Star 哦。

資料表設計

其實這一塊我個人是參考的 Zizaco/entrust 因為我覺得,大多數情況下,我們要用的角色和許可權節點都是針對使用者的。資料表設計如下:
file

可以看到關係如下 user -> role -> ability ,其中關係全部都是多對多關係。一個使用者可以擁有多個 role ,一個 ability 可以被分配給多個 role

鏈式方法設計

$user->ability('create user'); // 判斷是否有 create user 許可權。
$user->ability('owner', 'delete user'); // 判斷使用者是否擁有 owner 使用者組,且是否這個組擁有 delete user 許可權。
$user->ability(); // 返回一個 Ability 例項。
$user->roles;  // 讀取使用者所擁有的所有使用者組。
$user->roles(); // 獲取 Builder 例項。
$user->roles('owner'); // 檢查使用者是否擁有 owner 使用者組,擁有返回 model 例項,否則返回 false。
$user->ability()->roles(); // 讀取使用者所擁有的所有使用者組。返回的是一個 集合。可用集合所有方法。
$user->ability()->roles('owner'); // 檢查使用者是否擁有 owner 使用者組,擁有返回 model 例項,否則返回 false。
$user->ability()->all(); // 返回使用者擁有的所有許可權集合。
$user->ability()->all('create user'); // 檢查使用者是否擁有 create user 許可權,沒有返回 false ,有返回 ability 例項。

其中呼叫 $user->ability()->roles()$user->ability()->all() 都是返回的 集合 可以鏈式呼叫集合下的所有方法進一步操作。

ability 使用者 Trait

<?php

namespace Zhiyi\Plus\Models\Concerns;

use Zhiyi\Plus\Models\Role;
use Zhiyi\Plus\Services\UserAbility;

trait UserHasAbility
{
    /**
     * Abiliry service instance.
     *
     * @var \Zhiyi\Plus\Services\UserAbility
     */
    protected $ability;

    /**
     * User ability.
     *
     * @param array $parameters
     *        ability();
     *        ability($ability);
     *        ability($role, $ability);
     * @return mixed
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function ability(...$parameters)
    {
        if (isset($parameters[1])) {
            return ($role = $this->resolveAbility()->roels($parameters[0]))
                ? $role->ability($parameters[1])
                : false;
        } elseif (isset($parameters[0])) {
            return $this->resolveAbility()
                ->all($parameters[0]);
        }

        return $this->resolveAbility();
    }

    /**
     * The user all roles.
     *
     * @param string $role
     * @return mied
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function roles(string $role = '')
    {
        if ($role) {
            return $this->ability()->roles($role);
        }

        return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
    }

    /**
     * Resolve ability service.
     *
     * @return \Zhiyi\Plus\Services\UserAbility
     * @author Seven Du <shiweidu@outlook.com>
     */
    protected function resolveAbility()
    {
        if (! ($this->ability instanceof UserAbility)) {
            $this->ability = new UserAbility();
        }

        return $this->ability->setUser($this);
    }
}

Ability 例項

<?php

namespace Zhiyi\Plus\Services;

use Illuminate\Support\Collection;
use Zhiyi\Plus\Models\User as UserModel;
use Zhiyi\Plus\Contracts\Model\UserAbility as UserAbilityContract;

class UserAbility implements UserAbilityContract
{
    protected $user;

    /**
     * Get all roles or get first role.
     *
     * @param string $role
     * @return mixed
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function roles(string $role = '')
    {
        $roles = $this->user()
            ->roles()
            ->get()
            ->keyBy('name');

        if (! $role) {
            return $roles;
        }

        return $roles->get($role, false);
    }

    /**
     * Get all abilities or get first ability.
     *
     * @param string $ability
     * @return mixed
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function all(string $ability = '')
    {
        $roles = $this->roles();
        $roles->load('abilities');
        $abilities = $roles->reduce(function ($collect, $role) {
            return $collect->merge(
                $role->abilities->keyBy('name')
            );
        }, new Collection());

        if (! $ability) {
            return $abilities;
        }

        return $abilities->get($ability, false);
    }

    /**
     * Get user instance.
     *
     * @return \Zhiyi\Plus\Models\User
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function user(): UserModel
    {
        return $this->user;
    }

    /**
     * Set user model.
     *
     * @param \Zhiyi\Plus\Models\User $user
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function setUser(UserModel $user)
    {
        $this->user = $user;

        return $this;
    }
}

Role 模型所需程式碼

<?php

namespace Zhiyi\Plus\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * Get all abilities of the role.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function abilities()
    {
        return $this->belongsToMany(Ability::class, 'ability_role', 'role_id', 'ability_id');
    }

    /**
     * Get or check The role ability.
     *
     * @param string $ability
     * @return false|\User\Plus\Models\Ability
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function ability(string $ability)
    {
        return $this->abilities->keyBy('name')->get($ability, false);
    }
}

使用

然後我們開啟 User 模型檔案新增如下程式碼:

class User ...
{
    use UserHasAbility;
}

總結

其實性狀在 User 模型中只暴露了 rolesability 兩個公開方法。但是已經足以勝任使用者組許可權判斷邏輯了。

整個 ability 都是結合在集合之上的一些封裝,這樣是的程式碼呼叫更加優雅。


以上程式碼是在開發 ThinkSNS+ 中的實際真實程式碼。具體的實現可參考專案。
最後,開源不易,大家可以看下 ThinkSNS+ 程式,覺得不錯幫忙點一個 Star。?

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Seven 的程式碼太渣,歡迎關注我的新擴充包 medz/cors 解決 PHP 專案程式設定跨域需求。

相關文章