質問

私は長い間、アプリケーションのさまざまな領域でさまざまなユーザー設定や権限に合わせて、PHP でビットマスクまたはビットフィールドを使用する最適な方法を見つけようと努めてきました。私がこれまでで最も遠いところに来たのは、Stack Overflowのsvensによって貢献されたクラスからのものです 投稿 PHPのビットマスク設定は?. 。以下では、これを少し変更して、DEFINE の代わりにクラス定数を使用するように変更し、get メソッドに int のみが渡されるようにしました。以下にクラスの機能をテストするためのサンプル コードもいくつかあります。

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

以下のコメントでmcrumleyによって回答されました

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


いくつかのアイデア...このクラスを拡張して、他のクラスでも簡単に使用できるようにする方法を見つけたいと考えています。私が持っているとしましょう User クラスと Messages クラス。どちらも User そして Messages class はこのクラスを拡張し、(後で他のクラスとともに) 設定/権限にビットマスクを使用できるようになります。したがって、現在のクラス定数を変更して、それらを渡すことができるようにするか、または他のオプションを変更する必要があるでしょうか?私は実際には、サイト/スクリプトの他の部分で (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>';
    }
?>
役に立ちましたか?

解決

このビットマスキング部分については他の人がさらに説明してくれたので、ここではそれに集中します。

「もっと作るというアイデアは好きです 拡張可能/ジェネリックが違う クラスはこれを拡張して、 さまざまなセクション、よくわかりません まだやる方法」

@Charles の投稿に対するコメントより。

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

例えば:

<?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 オブジェクトをインスタンス化し、代わりにそれを使用するだけです。

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

これがあなたにとって何らかの役に立つことを願っていますが、そうでないとしても、これを読んでいる他の人にとってはおそらく役立つでしょう。

他のヒント

このタイプの他のクラスやコードサンプルでは、​​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

次に、読み取りアクセスと書き込みアクセスを持つユーザーを作成しましょう。

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_NONE0 & 0, 、これはゼロです。私が作成した「なし」権限は、ここでは実際には機能しないため、すぐに忘れてしまう可能性があります。)

あなたのクラスは何かをしています 少し違う, しかし、最終的な結果は同じです。ビットシフトを使用して、「オン」ビットを左に 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

これらの方法は 効果的に 同じですが、単純な AND 演算と OR 演算の方が、XOR 演算やビットシフトよりも読みやすいと思います。

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

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

私の提案はクラスを作ることです 抽象的な そしてその中で権限を定義していません。代わりに、それを継承するクラスを構築し、独自の権限を定義します。同じ権限を共有することを検討したくない場合 名前 無関係なビット フィールドにまたがる場合、それらのビット フィールドにクラス名をプレフィックスとして付けるのは非常に適切です。とにかくあなたはこれをするつもりだったと思います。

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

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

別の選択肢としては、 この回答のコードを大きな整数に使用する, これにより、巨大な整数を文字列として表現できるようになりますが、それに対してビット単位の演算を行うと、...面白い。(これを基数 2 にダウンコンバートし、予期される位置で文字列 "1" または "0" の substr チェックを実行することもできますが、パフォーマンスが大幅に低下します。)

私の提案は次のとおりです。

<?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 に対して $value を AND 演算する必要があります。

    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

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

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

私ならクラス内の許可フラグを削除し、ビジネス ロジック クラスにアウトソーシングします。

<編集>

データ構造 は 1 つのことを処理するエンティティです。データ。データはいかなる方法でも解釈されません。あ スタック, たとえば、 は、最後の項目を最初に与える、データを入れることができるデータ構造です。そしてここがポイントです: そこに何を入れても関係ない:整数、ユーザー オブジェクト、ポインター、車、象など、データの保存と取得を処理するだけです。

ビジネスの論理 一方、データ構造が相互にどのように相互作用するかを定義する場所です。ここで権限が定義され、ブログ投稿を作成した人がその投稿を編集でき、他の人は編集できないことを指定します。

これらはアプリケーションに対する 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() のパラメータはフラグ番号ではなく、単一ビットの整数でなければならないと定義します。デーモンによる set() の実装は、私が言いたいことです:

$this->value |= $n;

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

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

したがって、おそらくあなたは、頭の中でバイナリ計算を行う必要がなく、代わりにバイナリ計算をカプセル化して、より人間らしいインターフェイス (少なくとも数字ではなく名前) を提供して対話できるクラスを使いたいと考えているでしょう。大丈夫。しかし、それだけです。バイナリ値を渡すことで、ビットマスクを渡すことができます。バイナリ値が必要ない場合は、 列挙型 代わりにクラス すでに探しているものがあるかもしれません (具体的には FlagsEnum を確認してください)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top