途牛原創|大話權限中心的PHP架構之道

權限管理是無線運營系統中的核心模塊,經過訪問控制策略的配置,來約定人與資源的訪問關係。php

本文着重講解如何經過PHP來構建一個靈活、通用、安全的權限管理系統。html

關於權限

首先咱們來聊聊權限。git

權限系統一直以來是咱們應用系統不可缺乏的一個部分,若每一個應用系統都從新對系統的權限進行設計,以知足不一樣系統用戶的需求,將會浪費咱們很多寶貴時間,因此花時間來設計一個相對通用的權限系統是頗有意義的。github

系統目標:對應用系統的全部對象資源和數據資源進行權限控制,好比 應用系統的功能菜單、各個界面的按鈕、數據顯示的列以及各類行級數據 進行權限的操控。shell

權限模型

設計初期,咱們學習了Amazon的 IAM ,通過對比分析,最終咱們選用 RBAC3模型 來指導系統的設計工做。數據庫

RBAC認爲權限受權其實是Who、What、How的問題。在RBAC模型中,who、what、how構成了訪問權限三元組,也就是「Who對What(Which)進行How的操做」。編程

  • Who:權限的擁用者或主體(如Principal、User、Group、Role、Actor等等)json

  • What:權限針對的對象或資源(Resource、Class)。bootstrap

  • How:具體的權限(Privilege,正向受權與負向受權)。設計模式

  • Operator:操做。代表對What的How操做。也就是Privilege+Resource

  • Role:角色,必定數量的權限的集合。權限分配的單位與載體,目的是隔離User與Privilege的邏輯關係.

RBAC3模型

PHP架構之道

「如何用PHP構建咱們的權限中心」

接下來咱們將從 編碼規範、依賴管理、數據源架構、數據處理、單元測試 等方面來體驗一把PHP的神奇之旅。

編碼規範

好的編碼規範能夠改善軟件的可讀性,能夠促進團隊成長,能夠減小Bug,能夠下降維護成本,能夠。。。(這麼X,咱們必需要推廣)

PHP社區一直百花齊放,擁有大量的函數庫、框架和組件,於是PHP代碼遵循或儘可能接近同一個代碼風格就很是重要。

框架互操做組(即PHP標準組)發佈了一系列推薦風格。

權限中心的目錄結構:

-- /tuniu/rbac
|-- src
|   |-- App //應用建模層
|   |   |-- City.php
|   |   |-- Cms.php
|   |   |-- Menu.php
|   |-- App.php
|   |-- Auth.php
|   |-- Orm //ActiveRecord層
|   |   |-- App
|   |   |   |-- Resource
|   |   |   |   |-- Map.php
|   |   |   |-- Resource.php
|   |   |   |-- User.php
|   |   |-- App.php
|   |   |-- Log.php
|   |   |-- Role
|   |   |   |-- User.php
|   |   |-- Role.php
|   |   |-- Rule.php
|   |   |-- User.php
|   |-- Orm.php
|   |-- Utils.php
|-- tests //測試
|   |-- bootstrap.php
|   |-- fixtures
|   |   |-- null.yml
|   |   |-- rbac
|   |   |   |-- app.yml
|   |   |   |-- auth.yml
|   |   |   |-- role.yml
|   |-- rbac
|   |   |-- appTest.php
|   |   |-- authTest.php
|   |   |-- roleTest.php
|-- vendor
|-- composer.json
|-- composer.lock
|-- phpunit.xml
|-- README.md

PSR-2,權限應用資源類:

namespace Tuniu\Rbac\Orm\App;

use Tuniu\Rbac\Orm;
use Tuniu\Rbac\Orm\App;
use Tuniu\Rbac\Orm\App\Resource\Map;
use Tuniu\Rbac\Orm\Rule;
use Tuniu\Rbac\Orm\User;

class Resource extends Orm {}

PSR-4,命名空間的約定:

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

類名 文件路徑
\Tuniu\Rbac\Orm\App\Resource /tuniu/rbac/src/Orm/App/Resource.php
\Tuniu\Rbac\App\Cms /tuniu/rbac/src/App/Cms.php

依賴管理

Composer 是PHP中用來管理依賴(dependency)關係的工具。你能夠在本身的項目中聲明所依賴的外部工具庫(libraries),Composer會幫你安裝這些依賴的庫文件。

權限中心的依賴聲明:

{
    "name": "tuniu/rbac",
    "require": { //聲明依賴關係
        "php": ">=5.3.0",
        "squizlabs/php_codesniffer": "2.*", //PHP_CodeSniffer檢查代碼規範
        "php-activerecord/php-activerecord": "dev-master" //ActiveRecord
    },
    "require-dev": { //聲明開發依賴
        "phpunit/phpunit": "~4.6",
        "phpunit/dbunit": ">=1.2"
    },
    "autoload": {
        "psr-4": {
            "Tuniu\\Rbac\\": "src/" //命名空間
        }
    }
}

檢查代碼規範,執行單元測試。

$./vendor/bin/phpcs --config-set default_standard PSR2
$./vendor/bin/phpcs src
$./vendor/bin/phunit

^^^^^^ 眼澀,眼痠,眼疲勞,怎麼辦。。。

滴眼液

騷年,若是舒服了,就使勁往下滑動吧。。。

爭渡,爭渡,驚起一灘鷗鷺。

數據源架構

基於權限中心各表的關係(各類關聯,各類回調),採用傳統的模式,必將把精力耗在無盡的循環中。

因而乎,開始尋覓一種數據源的架構模式,Active Record很靠譜的出現了。

Active Record(中文名:活動記錄)是一種領域模型模式,特色是一個模型類對應關係型數據庫中的一個表,而模型類的一個實例對應表中的一行記錄。

PHP ActiveRecord 是一個基於ActiveRecord設計模式開發的開源PHP/ORM庫。它旨在大大簡化與數據庫的交互和減小手寫SQL語句。它不一樣於其餘的ORM,你不須要使用任何的代碼生成器,也不費勁去手寫、維護模型層的表映射文件。這個庫的靈感來自Ruby on Rails,所以它也借鑑Ruby on Rails的想法和實現。(誰用誰知道)

下面介紹下這個小夥伴給編程帶來的快樂:

  • Validation(數據驗證)

    • validates_presence_of

    • validates_inclusion_of

場景(角色表數據約定)

/**
 * 1:約定角色類型的範圍
 * 2:約定角色狀態的範圍
 */
public static $validates_inclusion_of = array(
    array('f_type', 'in' => array('role', 'group', 'department', 'member')),
    array('f_status', 'in' => array(1,2))
);

/**
 * 設定角色名稱、角色狀態、角色類型、角色描述不能爲空
 */
public static $validates_presence_of = array(
    array('f_name'),
    array('f_status'),
    array('f_type'),
    array('f_desc')
);

//錄入數據不知足約定條件,就沒法保存,不再用擔憂那些髒髒的數據。
  • Callback(回調)

    • before_save

    • before_create

    • before_update

    • before_destroy

    • after_save

    • after_create

    • after_update

    • after_destroy

場景1(角色表操做記錄)

//定義回調函數
public static $before_save = array('setMisc');

//每當角色表保存以前,都默默的把數據格式好,好開心。。。
public function setMisc()
{
    //建立時間,建立人
    $this->is_new_record() && ($this->f_create_at = date("Y-m-d H:i:s"));
    $this->is_new_record() && ($this->f_create_by = $this->op);
    //更新時間,更新人
    $this->f_update_by = $this->op; //操做人
    $this->f_update_at = date("Y-m-d H:i:s"); //操做時間
}

場景2(新增資源,推送資源映射表):

//定義回調函數
public static $after_save = array('syncRelations');

//每當資源保存以後,自動把資源數據中的映射字段值,推送給資源映射表。
public function syncRelations()
{
    //獲取資源的映射字段
    $mapFiledValue = $this->getMapFiledValue();

    $mapFiledValue
        ? $this->addMap($mapFiledValue)
        : Map::removeAllByResourceId($this->id);
}

場景3(角色刪除):

//定義回調函數
public static $after_destroy = array('deleteRelations');

//每當角色刪除時,自動把系統中角色的成員和角色的資源規則清空,並且是事務的。
public function deleteRelations()
{
    UserRelations::removeAllByRoleId($this->id);
    RuleRelatinos::removeAllByRoleId($this->id);
}
  • Association(關聯)

    • has_many

場景(新增角色用戶)

//定義角色表和角色用戶表的關係
public static $has_many = array(
    array(
        'relations',
        'foreign_key' => 'f_role_id',
        'class_name' => "\\Tuniu\\Rbac\\Orm\\Role\\User",
    )
);

//新增角色用戶,默默的把role_id傳遞給了角色用戶表,此處若是用SQL,簡直不忍直視。
Role::first()->create_relation(
    array(
        'f_user_id' => $uid
    )
);
  • 事務(一致性與安全性)

權限系統中數據一致性和數據安全性的重要性是不言而喻,不用事務會被BS的。

在此咱們鄭重承諾,權限系統中每一次數據增刪改請求,都是事務處理的。

好比角色保存:

self::transaction(
    function () use ($params, &$role) {

        $role->f_name   = $params['name'];
        $role->f_status = $params['status'];
        $role->f_type   = $params['type'];
        $role->f_desc   = $params['desc'];

        if ($role->is_invalid()) {
            throw new \Exception('角色相關操做失敗', '900202');
        }

        $role->save();
    }
);

篇幅有限,這個小夥伴的戰鬥力場景遠甚於此。

坦白的說,用AR是一種編程享受。

單元測試(持續交付)

一切都如此的完美,沒有測試,又如何能夠證實這件事情的完美,又如何能夠保障交付的質量。

PHPUnit 是一個輕量級的PHP測試框架。它是在PHP5下面對JUnit3系列版本的完整移植,是xUnit測試框架家族的一員(它們都基於模式先鋒Kent Beck的設計)。

單元測試是一種提升軟件質量很是有效的方法,但很重要的是咱們要去實踐和體會。

簡單的介紹下權限管理中的角色行爲測試用例:

角色行爲測試

  • 數據集(YAML)

t_rbac_user:
    -
        f_id: 1
        f_name: "zhaoyang2"
        f_create_at: "2013-10-10 17:04:05"
t_rbac_role_user:
    -
        f_id: 1
        f_user_id: 1
        f_role_id: 1
t_rbac_role:
    -
        f_id: 1
        f_name: "運營研發部-1"
        f_status: 1,
        f_type: "department"
        f_desc: "咱們是運營研發部-1"
        f_create_at: "2013-10-10 17:04:05"
        f_create_by: "zhaoyang2"
        f_update_by: "zhaoyang2"
  • 測試代碼:

//預設數據集
public function getDataSet()
{
    return new \PHPUnit_Extensions_Database_DataSet_YamlDataSet(
        fixture('rbac/role.yml')
    );
}

/**
 * 權限操做-異常驗證
 * @expectedException Exception
 * @expectedExceptionMessage 您無權運營當前數據
 */
public function testRoleDeleteException()
{
    Role::first()->remove();
}

/**
 * 權限操做-刪除驗證
 */
public function testRoleDelete()
{
    Role::first()->remove("zhaoyang2");

    //驗證數據行
    $this->assertEquals(1, $this->getConnection()->getRowCount(Role::$table_name));
    $this->assertEquals(1, $this->getConnection()->getRowCount(UserRelation::$table_name));
}

Rbac

鑑權用例和應用管理用例,遠比這個複雜,感興趣的同窗能夠去 Fork 一把,瞭解下PHPUNIT的魅力。

PHPUNIT很強大,想合理運用的話,沒有任何捷徑,開始寫測試用例吧。。

結束語

其實說架構算上下,就是和你們分享下權限中心的PHP之道。

高效便捷的使用PHP服務咱們的工做。

多交流,多分享,書寫更好的PHP代碼,享受編程和技術所帶來的快樂。

相關文章
相關標籤/搜索