14

私は、アプリケーションのさまざまな領域でさまざまなユーザー設定とアクセス許可に対して、PHP でビットマスクまたはビットフィールドを使用する最良の方法を長い間見つけようとしてきました。私がこれまでに行った中で最も遠いのは、Stack Overflow の投稿Bitmask in PHP for settings? で svensによって提供されたクラスからのものです。. DEFINE の代わりにクラス定数を使用するように変更し、get メソッドに int のみが渡されるようにしました。以下に、クラスの機能をテストするためのサンプル コードをいくつか示します。

このクラスをさらに改善するための提案/コードを探しているので、アプリケーションで設定や場合によってはユーザー権限に使用できます。

以下のコメントで mcrumley が回答

さらに、定数の番号付けについて質問があります。このタイプの他のクラスとコード サンプルでは、​​2 の累乗でリストされているものがあります。ただし、定数に 1、2、3、4、5、6 の番号を付けても、私が知る限り、同じように機能するようです。 1、2、4、8、16などの代わりに。定数を変更する必要があるかどうかを誰かが明確にすることもできますか?


いくつかのアイデア...このクラスを拡張して、他のクラスで簡単に使用できるようにする方法を本当に考えたいと思います。UserクラスとクラスがあるとしましょうMessagesUserとの両方Messagesクラスはこのクラスを拡張し、設定/権限にビットマスクを使用できるようになります (後で他のクラスと共に)。それで、現在のクラス定数を変更して、それらを渡すことができるようにするか、他のオプションを指定する必要がありますか? サイト/スクリプトの他の部分で (define('PERM_READ', 1);) を定義する必要はなく、ある程度カプセル化したままにしておく必要がありますが、柔軟性もあります。私はアイデアを受け入れます。設定や権限のために他の複数のクラスで使用すると言ったように、これを堅固で柔軟にしたいと考えています。おそらく、ある種の配列を使用する必要がありますか? 上記にリンクされた私の以前の質問の@Svensは、「いくつかの自動魔法のゲッター/セッターまたはArrayAccessを実装して、さらに素晴らしいものにします。 – svens」というコメントを投稿しました。

可能であれば、サンプル ソース コードを含めてください。

<?php

class BitField {

    const PERM_READ = 0;
    const PERM_WRITE = 1;
    const PERM_ADMIN = 2;
    const PERM_ADMIN2 = 3;
    const PERM_ADMIN3 = 4;

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
        if (is_int($n)) {
            return ($this->value & (1 << $n)) != 0;
        }else{
            return 0;
        }
    }

    public function set($n, $new=true) {
        $this->value = ($this->value & ~(1 << $n)) | ($new << $n);
    }

    public function clear($n) {
        $this->set($n, false);
    }
}
?>

使用例...

<?php
    $user_permissions = 0; //This value will come from MySQL or Sessions
    $bf = new BitField($user_permissions);

    // Turn these permission to on/true
    $bf->set($bf::PERM_READ);
    $bf->set($bf::PERM_WRITE);
    $bf->set($bf::PERM_ADMIN);
    $bf->set($bf::PERM_ADMIN2);
    $bf->set($bf::PERM_ADMIN3);

    // Turn permission PERM_ADMIN2 to off/false
    $bf->clear($bf::PERM_ADMIN2); // sets $bf::PERM_ADMIN2 bit to false

    // Get the total bit value
    $user_permissions = $bf->getValue();

    echo '<br> Bitmask value = ' .$user_permissions. '<br>Test values on/off based off the bitmask value<br>' ;

    // Check if permission PERM_READ is on/true
    if ($bf->get($bf::PERM_READ)) {
        // can read
        echo 'can read is ON<br>';
    }

    if ($bf->get($bf::PERM_WRITE)) {
        // can write
        echo 'can write is ON<br>';
    }

    if ($bf->get($bf::PERM_ADMIN)) {
        // is admin
        echo 'admin is ON<br>';
    }

    if ($bf->get($bf::PERM_ADMIN2)) {
        // is admin 2
        echo 'admin 2 is ON<br>';
    }

    if ($bf->get($bf::PERM_ADMIN3)) {
        // is admin 3
        echo 'admin 3 is ON<br>';
    }
?>
4

5 に答える 5

15

このタイプの他のクラスとコードサンプルでは、​​2 の累乗でリストされているものがありますが、定数の代わりに 1,2,3,4,5,6 の番号を付けても、私が知る限り、同じように機能するようです。 1,2,4,8,16など。定数を変更する必要があるかどうかを誰かが明確にすることもできますか?

コードが既にそれを処理しているため、その必要はありません。この説明は少し回り道になります。

ビット フィールドが2 の累乗として扱われる理由は、2 の累乗がそれぞれ 1 つのビットで表されるためです。これらの個々のビットは、ビット単位の OR を使用して、渡される単一の整数にすることができます。低レベル言語では、たとえば構造体よりも数値を渡す方が「簡単」です。

これがどのように機能するかを示しましょう。2 のべき乗を使用していくつかのアクセス許可を設定しましょう。

define('PERM_NONE', 0);
define('PERM_READ', 1);
define('PERM_WRITE', 2);
define('PERM_EDIT', 4);
define('PERM_DELETE', 8);
define('PERM_SUPER', 16);

PHP インタラクティブ プロンプトで、これらのアクセス許可のビット値を調べてみましょう。

php > printf('%08b', PERM_SUPER);
00010000
php > printf('%08b', PERM_DELETE);
00001000
php > printf('%08b', PERM_EDIT);
00000100
php > printf('%08b', PERM_WRITE);
00000010
php > printf('%08b', PERM_READ);
00000001
php > printf('%08b', PERM_NONE);
00000000

次に、READ アクセスと WRITE アクセスを持つユーザーを作成しましょう。

php > printf('%08b', PERM_READ | PERM_WRITE);
00000011

または、読み取り、書き込み、削除はできるが編集はできないユーザー:

php > printf('%08b', PERM_READ | PERM_WRITE | PERM_DELETE);
00001011

ビットごとの AND を使用して許可をチェックし、結果がゼロでないことを確認できます。

php > $permission = PERM_READ | PERM_WRITE | PERM_DELETE;
php > var_dump($permission & PERM_WRITE); // This won't be zero.
int(2)
php > var_dump($permission & PERM_EDIT); // This will be zero.
int(0)

PERM_NONE & PERM_NONE(これは 0 であることに注意してください0 & 0。作成した "none" パーミッションは実際にはここでは機能せず、すぐに忘れてしまう可能性があります。)

あなたのクラスは少し違うことをしていますが、最終結果は同じです。ビットシフトを使用して、「オン」ビットを左にX回移動します。Xは許可の数です。実際には、これは 2 をパーミッションの値で累乗しています。デモンストレーション:

php > echo BitField::PERM_ADMIN3;
4
php > echo pow(2, BitField::PERM_ADMIN3);
16
php > printf('%08b', pow(2, BitField::PERM_ADMIN3));
00010000
php > echo 1 << BitField::PERM_ADMIN3;
16
php > printf('%08b', 1 << BitField::PERM_ADMIN3);
00010000

これらの方法は事実上同一ですが、単純な ANDing と ORing の方が XORing と bit-shifting よりも読みやすいと思います。

このクラスをさらに改善するための提案/コードを探しているので、アプリで設定や場合によってはユーザー権限に使用できます。

1 つの提案と 1 つの警告があります。

私の提案は、クラスを抽象化し、その中に権限を定義しないことです。代わりに、それを継承して独自のアクセス許可を定義するクラスを構築します。無関係なビット フィールド間で同じパーミッション名を共有することを検討したくない場合は、それらにクラス名をプレフィックスとして付けるのが適切です。とにかくこれを行うつもりだったと思います。

私の警告は単純ですが、悲惨です: PHP は 31 ビットより大きい整数を確実に表現することはできません。 実際、64 ビット システムでコンパイルした場合は、63 ビット整数しか表現できません。これは、アプリケーションを一般大衆に配布する場合、組み込みの数学関数を使用する場合 、31 個を超えるアクセス許可に制限されることを意味します。

GMP 拡張機能には、任意の長さの整数に対して機能するビット単位の演算が含まれています。

別のオプションとして、この回答のコードを large integersで使用することもできます。これにより、巨大な整数を文字列として表すことができますが、ビット単位の操作を行うことは...興味深いかもしれません。(基数 2 にダウンコンバートしてから、予想される位置で文字列 "1" または "0" の substr チェックを実行できますが、これはパフォーマンスを大幅に低下させます。)

于 2011-03-23T22:16:05.053 に答える
14

他の人がこれのビットマスキングビットをさらに説明するのを手伝ってくれたので、私は集中します

「私はそれをより拡張可能/汎用的にして、さまざまなクラスがこれを拡張してさまざまなセクションに使用できるようにするというアイデアが好きです。まだそれを行う方法がわかりません。」

@Charlesの投稿へのコメントから。

Charlesが正しく言ったように、機能を抽象クラスに抽出し、実際の「設定」(この場合はアクセス許可)を派生具象クラスに入れることで、ビットマスククラスの機能を再利用できます。

例えば:

<?php

abstract class BitField {

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
        if (is_int($n)) {
            return ($this->value & (1 << $n)) != 0;
        }else{
            return 0;
        }
    }

    public function set($n, $new=true) {
        $this->value = ($this->value & ~(1 << $n)) | ($new << $n);
    }

    public function clear($n) {
        $this->set($n, false);
    }
}

class UserPermissions_BitField extends BitField
{
    const PERM_READ = 0;
    const PERM_WRITE = 1;
    const PERM_ADMIN = 2;
    const PERM_ADMIN2 = 3;
    const PERM_ADMIN3 = 4;
}

class UserPrivacySettings_BitField extends BitField
{
    const PRIVACY_TOTAL = 0;
    const PRIVACY_EMAIL = 1;
    const PRIVACY_NAME = 2;
    const PRIVACY_ADDRESS = 3;
    const PRIVACY_PHONE = 4;
}

そして、使用法は単純に次のようになります。

<?php
$user_permissions = 0; //This value will come from MySQL or Sessions
$bf = new UserPermissions_BitField($user_permissions); 

// turn these permission to on/true
$bf->set($bf::PERM_READ);
$bf->set($bf::PERM_WRITE);
$bf->set($bf::PERM_ADMIN);
$bf->set($bf::PERM_ADMIN2);
$bf->set($bf::PERM_ADMIN3);

プライバシー設定を設定するには、新しいUserPrivacySettings_BitFieldオブジェクトをインスタンス化し、代わりにそれを使用します。

このように、オプションを表す定数のセットを定義するだけで、アプリケーションが必要とする数の異なるビットフィールドオブジェクトのセットを作成できます。

これがあなたの役に立つことを願っていますが、そうでない場合は、これを読んでいる他の誰かの役に立つかもしれません。

于 2011-03-26T00:48:55.810 に答える
7

これが私の提案です:

<?php

class BitField {

    const PERM_READ = 1;
    const PERM_WRITE = 2;
    const PERM_ADMIN = 4;
    const PERM_ADMIN2 = 8;
    const PERM_ADMIN3 = 16;

    private $value;

    public function __construct($value=0) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }

    public function get($n) {
                return $this->value & $n;
    }

    public function set($n, $new=true) {
        $this->value |= $n;
    }

    public function clear($n) {
        $this->value &= ~$n;
    }

}
?>

ご覧のとおり、計算を簡略化するために1、2、4、8など(2の累乗)を使用しました。1つのパーミッションを1つのビットにマップすると、次のようになります。

0 0 0 0 0 0 0 1 = PERM_READ = 1
0 0 0 0 0 0 1 0 = PERM_WRITE = 2
0 0 0 0 0 1 0 0 = PERM_ADMIN = 4
etc...

次に、論理演算を使用できます。たとえば、最初は次のようになります。

    0 0 0 0 0 0 0 1 = PERM_READ = 1

書き込み権限を追加する場合は、ビット単位のOR演算子のみを使用する必要があります。

    0 0 0 0 0 0 0 1 = PERM_READ = 1
OR  0 0 0 0 0 0 1 0 = PERM_WRITE = 2
=   0 0 0 0 0 0 1 1 = both bits enabled R & W

1ビットを削除するには、$ valueと〜$ bitを使用する必要があります。たとえば、書き込みビットを削除します。

    0 0 0 0 0 0 1 1 = both bits enabled R & W
AND 1 1 1 1 1 1 0 1 = Bitwise negated PERM_WRITE
=   0 0 0 0 0 0 0 1 = result, only the R bit

最後に、1ビットが有効になっているかどうかをテストする場合は、テストするPERM_XXXに対してAND$valueを実行する必要があります。

    0 0 0 0 0 0 1 1 = both bits enabled R & W
AND 0 0 0 0 0 0 1 0 = Want to test PERM_WRITE
=   0 0 0 0 0 0 1 0 = result

結果がゼロでない場合は許可があり、そうでない場合は許可がありません。

于 2011-03-23T22:06:52.593 に答える
4

あなたのクラスで私が目にする最大の間違いは、ビジネス ロジックをデータ構造に混在させていることです。クラスの目的は、複数のブール値 (つまり、true/false) を単一の整数に格納することです。これはクラスで行う必要はありませんが、便利です。そして、それがその目的です。

クラス内のパーミッション フラグを削除し、ビジネス ロジック クラスにアウトソーシングします。

<編集>

データ構造は、データという1 つのことを処理するエンティティです。データは決して解釈されません。たとえば、stackは、何かを入れることができるデータ構造であり、最後のアイテムを最初に提供します。ポイントは次のとおりです。整数、ユーザー オブジェクト、ポインター、車、象など、何を入れてもかまいません。データの保存と取得を処理するだけです。

一方、ビジネス ロジックでは、データ構造が相互にやり取りする方法を定義します。ここでパーミッションが定義され、ブログ投稿を作成した人は編集可能であり、他の人は編集できないことを示します。

これらは、アプリケーションの 2 つの根本的に異なるビューであり、混在させるべきではありません。パーミッションを別のデータ構造 (整数の配列、またはPermission オブジェクトのハッシュ テーブル、またはその他のデータ構造など) に保存したり、BitField データ構造に他のフラグを保存したりできます (ブール値の設定など)。たとえば、「ニュースレターを受け取りたい」や「メール アドレスが確認されました」など)。

</編集>

もう 1 つの改善点は、これらの定数に 16 進値を使用することです。これにより、16 番目の値を引き続き読み取ることができます。(むしろ、定数宣言でビットシフト演算子を使用することをお勧めします。これはさらに読みやすくなりますが、現在の PHP インタープリターではパフォーマンス上の理由からこれは不可能です。)

class Permission {
    const READ     = 0x0001;
    const UPDATE   = 0x0002;
    const DELETE   = 0x0004;
    const COMMENT  = 0x0008;
    const GRANT    = 0x0010;
    const UNDELETE = 0x0020;
    const WHATEVER = 0x0040;
}

$permissions = new BitField();
$permissions->set(Permission::READ);
$permissions->set(Permission::WRITE);

<編集>

16 進数値のない同じクラスは、特にフラグを追加すると読みにくくなります。

class Permission {
    const READ         = 1;
    const UPDATE       = 2;
    const DELETE       = 4;
    const COMMENT      = 8;
    const GRANT        = 16;
    const UNDELETE     = 32;
    const WHATEVER     = 64;
    const PERMISSION8  = 128;
    const PERMISSION9  = 256;
    const PERMISSION10 = 512;
    const PERMISSION11 = 1024;
    const PERMISSION12 = 2048;
    const PERMISSION13 = 4096;
    const PERMISSION14 = 8192;
    const PERMISSION15 = 16384;
    const PERMISSION16 = 32768; # the 16th value I mentioned above. Would
                                # you immediately recognize this value as 2^16?
                                # I wouldn't.
    const PERMISSION17 = 65536;
    const PERMISSION18 = 131072;
    const PERMISSION19 = 262144;
}

</編集>

さらに、set() のパラメーターは、フラグ番号ではなく、1 ビット整数でなければならないことを定義します。デーモンによる set() 実装は、私が意味するものです:

$this->value |= $n;
于 2011-03-30T17:19:43.953 に答える
0

「私はそれをより拡張可能/汎用にするというアイデアが好きなので、さまざまなクラスがこれを拡張してさまざまなセクションで使用できるようにします。まだそれを行う方法がわかりません。」

それをしないでください、それにはさまざまな理由があります。特定の順序ではなく、簡単に言うと、機能クラスをデータ オブジェクトから分離します。継承を必要としないものは拡張しないでください。代わりにプロパティを使用してください。通常、拡張クラスは、ビットマスク クラスと密接に結合する必要はまったくありません。さらに、PHP では、1 つのクラスからしか拡張できません。そのような限られた用途にそれを利用する場合、拡張オブジェクトはすでにその機能を焼き尽くしています。

したがって、脳内で 2 進数の計算を行う必要はなく、その代わりに 2 進数の計算をカプセル化し、より人間的な (少なくとも数値ではなく名前で) 相互作用するインターフェイスを提供するクラスを用意することをお勧めします。罰金。ただ、それだけです。バイナリ値を渡すことで、ビットマスクを渡すことができます。バイナリ値が必要ない場合は、代わりにenumクラスが既に探しているものである可能性があります (特定の FlagsEnum を確認してください)。

于 2011-04-03T09:58:03.970 に答える