Diese PHP-Bitfeldklasse für Einstellungen/Berechtigungen verbessern?

Lesezeit: 4 Minuten

Diese PHP Bitfeldklasse fur EinstellungenBerechtigungen verbessern
JasonDavis

Ich versuche seit langem herauszufinden, wie ich Bitmasken oder Bitfelder in PHP für verschiedene Bereiche meiner Anwendung für unterschiedliche Benutzereinstellungen und Berechtigungen am besten verwenden kann. Am weitesten bin ich bisher von einer Klasse gekommen, die von svens im Stack Overflow-Beitrag beigesteuert wurde Bitmaske in PHP für Einstellungen?. Ich habe es unten leicht modifiziert, indem ich es geändert habe, um Klassenkonstanten anstelle von DEFINE zu verwenden und sicherzustellen, dass der get-Methode nur ein int übergeben wird. Ich habe auch etwas Beispielcode, um die Funktionalität der Klasse weiter unten zu testen.

Ich suche nach Vorschlägen/Code, um diese Klasse noch weiter zu verbessern, damit sie in meiner Anwendung für Einstellungen und in einigen Fällen Benutzerberechtigungen verwendet werden kann.

Beantwortet im Kommentar unten von mcrumley

Außerdem habe ich noch eine Frage zur Nummerierung meiner Konstanten. In anderen Klassen und Codebeispielen für diesen Typ werden die Dinge in Potenzen von 2 aufgelistet. Es scheint jedoch, soweit ich das beurteilen kann, gleich zu funktionieren, selbst wenn ich meine Konstanten 1,2,3,4,5,6 nummeriere statt 1, 2, 4, 8, 16 usw. Kann mir also auch jemand klären, ob ich meine Konstanten ändern soll?


Einige Ideen… Ich würde wirklich gerne einen Weg finden, diese Klasse zu erweitern, damit sie einfach mit anderen Klassen verwendet werden kann. Sagen wir, ich habe ein User klasse und a Messages Klasse. Beide User und Messages class erweitert diese Klasse und kann die Bitmaske für ihre Einstellungen/Berechtigungen verwenden (zusammen mit anderen Klassen später). Vielleicht sollten also die aktuellen Klassenkonstanten geändert werden, damit sie übergeben werden können oder eine andere Option? Ich möchte wirklich lieber nicht definieren (define(‘PERM_READ’, 1;)) in anderen Teilen der Site/des Skripts und möchte es etwas gekapselt, aber auch flexibel halten; Ich bin offen für Ideen. Ich möchte, dass dies felsenfest und flexibel ist, wie ich bereits sagte, um es mit mehreren anderen Klassen für Einstellungen oder Berechtigungen zu verwenden. Vielleicht sollte eine Art Array verwendet werden? @Svens aus meiner vorherigen Frage, die oben verlinkt wurde, hat einen Kommentar mit “Implementieren Sie einige automagische Getter/Setter oder ArrayAccess für zusätzliche Ehrfurcht. – svens” gepostet. Was halten Sie auch von so etwas?

Fügen Sie nach Möglichkeit Beispielquellcode hinzu.

<?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);
    }
}
?>

Anwendungsbeispiel…

<?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>';
    }
?>

  • Da Sie dies für Ihre Web-App-Einstellungen verwenden möchten, warum implementieren Sie nicht einen einfachen ACL-Manager?

    – CoolStraw

    21. März ’11 um 16:29

  • @CoolStraw Ich kann dies tun, aber ich könnte auch Bitmasken innerhalb einer ACL-Klasse implementieren

    – JasonDavis

    21. März ’11 um 16:47

  • Die Konstanten sind als 0 bis was auch immer korrekt. Der Code 1 << $n erhebt 2 mit der $n-ten Potenz durch Bitverschiebung. 1<<0 == 1, 1<<1 == 2, 1<<2 == 4, 1<<3 == 8, usw.

    – mcrumley

    23. März ’11 um 21:39 Uhr

  • @mcrumley danke für die Klarstellung

    – JasonDavis

    23. März ’11 um 21:42

Diese PHP Bitfeldklasse fur EinstellungenBerechtigungen verbessern
Charles

In anderen Klassen und Codebeispielen für diesen Typ werden die Dinge in Potenzen von 2 aufgelistet, aber es scheint, soweit ich das beurteilen kann, gleich zu funktionieren, auch wenn ich meine Konstanten 1,2,3,4,5,6 statt nummeriere 1,2,4,8,16 usw. Kann mir also auch jemand klären, ob ich meine Konstanten ändern soll?

Das müssen Sie nicht, denn der Code kümmert sich bereits darum. Diese Erklärung wird ein bisschen umständlich sein.

Der Grund, warum Bitfelder behandelt werden als Zweierpotenzen ist, dass jede Zweierpotenz durch ein einzelnes Bit dargestellt wird. Diese einzelnen Bits können bitweise ODER-verknüpft zu einer einzelnen ganzen Zahl werden, die weitergegeben werden kann. In niedrigeren Sprachen ist es “einfacher”, eine Zahl zu übergeben als beispielsweise eine Struktur.

Lassen Sie mich zeigen, wie das funktioniert. Lassen Sie uns einige Berechtigungen mit der Zweierpotenz einrichten:

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

Sehen wir uns die Bitwerte dieser Berechtigungen an der interaktiven PHP-Eingabeaufforderung an:

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

Lassen Sie uns nun einen Benutzer erstellen, der über LESE- und SCHREIB-Zugriff verfügt.

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

Oder ein Benutzer, der lesen, schreiben, löschen, aber nicht bearbeiten kann:

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

Wir können die Berechtigung mit bitweisem UND überprüfen und sicherstellen, dass das Ergebnis nicht Null ist:

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)

(Es ist erwähnenswert, dass PERM_NONE & PERM_NONE ist 0 & 0, was null ist. Die von mir erstellte Berechtigung “none” funktioniert hier nicht und kann sofort vergessen werden.)

Deine Klasse macht was etwas anders, aber das Endergebnis ist identisch. Es verwendet Bitverschiebung, um ein “Ein”-Bit X-mal nach links zu verschieben, wobei X die Nummer der Berechtigung ist. Tatsächlich dies erhöht den Wert der Berechtigung um 2. Eine Demonstration:

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

Diese Methoden sind zwar effektiv identisch, würde ich argumentieren, dass einfaches UND-Verknüpfen und ODER-Verknüpfen einfacher zu lesen ist als das XOR-Verknüpfen und Bit-Verschieben.

Ich suche nach Vorschlägen/Code, um diese Klasse noch weiter zu verbessern, damit sie in meiner App für Einstellungen und in einigen Fällen Benutzerberechtigungen verwendet werden kann.

Ich habe einen Vorschlag und eine Warnung.

Mein Vorschlag wäre, die Klasse zu machen abstrakt und keine Berechtigungen darin zu definieren. Erstellen Sie stattdessen Klassen, die davon erben, und definieren Sie ihre eigenen Berechtigungen. Sie möchten nicht in Erwägung ziehen, dieselbe Berechtigung zu teilen Namen über nicht verwandte Bitfelder hinweg und ihnen Klassennamen voranzustellen ist ziemlich vernünftig. Ich gehe davon aus, dass Sie das sowieso tun wollten.

Meine Warnung ist einfach, aber schrecklich: PHP kann eine ganze Zahl, die größer als 31 Bit ist, nicht zuverlässig darstellen. Tatsächlich kann es nur 63-Bit-Ganzzahlen darstellen, wenn es auf einem 64-Bit-System kompiliert wird. Dies bedeutet, dass Sie, wenn Sie Ihre Bewerbung an die Öffentlichkeit verteilen, beschränkt auf nicht mehr als 31 Berechtigungen wenn Sie die integrierten mathematischen Funktionen verwenden möchten.

Die GMP-Erweiterung enthält bitweise Operationen, die mit ganzen Zahlen beliebiger Länge funktionieren können.

Eine andere Option könnte darin bestehen, Code aus dieser Antwort für große Ganzzahlen zu verwenden, wodurch Sie eine große Ganzzahl als String darstellen können, obwohl bitweise Operationen damit … interessant sein könnten. (Sie könnten es auf Basis-2 herunterkonvertieren und dann an der erwarteten Stelle eine substr-Prüfung auf die Zeichenfolge “1” oder “0” durchführen, aber das wird eine enorme Leistungseinbuße sein.)

  • danke für die erklärungen, jeder hat eine etwas andere antwort auf meine etwas verwandten fragen, so dass ich es nach und nach immer besser werde. Ich mag die Idee, es erweiterbarer / allgemeiner zu machen, damit verschiedene Klassen dies erweitern und für verschiedene Abschnitte verwenden können

    – JasonDavis

    24. März ’11 um 1:06

Andere haben geholfen, die Bit-Maskierung weiter zu erklären, also werde ich mich darauf konzentrieren

“Ich mag die Idee, es erweiterbarer / allgemeiner zu machen, damit verschiedene Klassen dies erweitern und für verschiedene Abschnitte verwenden können. Ich bin mir nur noch nicht sicher, wie das geht.”

aus Ihrem Kommentar zum Post von @Charles.

Wie Charles richtig sagte, können Sie die Funktionalität Ihrer Bitmask-Klasse wiederverwenden, indem Sie die Funktionalität in eine abstrakte Klasse extrahieren und die tatsächlichen “Einstellungen” (in diesem Fall Berechtigungen) in abgeleitete konkrete Klassen einfügen.

Beispielsweise:

<?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;
}

Und dann wird die Verwendung einfach:

<?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);

Und um Datenschutzeinstellungen festzulegen, instanziieren Sie einfach ein neues UserPrivacySettings_BitField-Objekt und verwenden es stattdessen.

Auf diese Weise können Sie so viele verschiedene Sätze von BitField-Objekten erstellen, wie Ihre Anwendung erfordert, indem Sie einfach einen Satz von Konstanten definieren, die Ihre Optionen darstellen.

Ich hoffe, dass dies für Sie von Nutzen ist, aber wenn nicht, wird es vielleicht jemand anderem, der dies liest, von Nutzen sein.

  • Ich mag das. Passen Sie nur auf, dass es aufgrund eines Integer-Überlaufs nicht über 32/64-Berechtigungen hinaus ohne weitere Arbeit funktioniert.

    – Davidsheldon

    20. August 12 um 15:50 Uhr

Hier mein Vorschlag:

<?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;
    }

}
?>

Wie Sie sehen, habe ich 1, 2, 4, 8 usw. (Potenzen von 2) verwendet, um die Berechnungen zu vereinfachen. Wenn Sie einem Bit eine Berechtigung zuordnen, haben Sie:

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...

Dann können Sie logische Verknüpfungen verwenden, zum Beispiel haben Sie dies zunächst:

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

Wenn Sie Schreibberechtigungen hinzufügen möchten, müssen Sie nur den bitweisen ODER-Operator verwenden:

    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

Um ein Bit zu entfernen, müssen Sie $value & ~$bit verwenden, zum Beispiel das Schreibbit entfernen:

    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

Wenn Sie schließlich testen möchten, ob ein Bit aktiviert ist, müssen Sie die Operation mit dem UND-Wert von UND mit dem PERM_XXX, das Sie testen möchten, verknüpfen:

    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

Wenn das Ergebnis nicht Null ist, haben Sie die Berechtigung, andernfalls nicht.

  • tolle Erklärung +1 hat mir geholfen, etwas besser zu verstehen als vorher und den Unterschied darin zu sehen, wie meine Klasse es jetzt macht

    – JasonDavis

    24. März ’11 um 1:08

  • Ich sollte mich schlagen. Ich habe Code verwendet, der solche Dinge implementiert, und ich verstehe ehrlich gesagt nie die Grundlagen. Ich schäme mich, zuzugeben, dass ich in einem Kurs für Computerorganisation und Assemblersprache bin.

    – Jeremy Dentel

    27. März ’11 um 6:28

Diese PHP Bitfeldklasse fur EinstellungenBerechtigungen verbessern
Seelenverschmelzung

Der größte Fehler, den ich in Ihrem Kurs sehe, ist, dass Sie Geschäftslogik in eine Datenstruktur mischen. Der Zweck Ihrer Klasse besteht darin, mehrere boolesche Werte (dh wahr/falsch) in einer einzigen Ganzzahl zu speichern. Das geht nicht verfügen über in einer Klasse gemacht werden, aber es ist bequem. Und das ist sein Zweck.

Ich würde die Berechtigungsflags in der Klasse löschen und in Ihre Geschäftslogikklassen auslagern.

EIN Datenstruktur ist eine Entität, die eines verarbeitet: Daten. Die Daten werden in keiner Weise interpretiert. EIN Stapel, zum Beispiel, ist eine Datenstruktur, in die Sie Dinge einfügen können, die Ihnen das letzte Element zuerst liefert. Und hier ist der Punkt: Es ist egal, was du da reinsteckst: Ganzzahlen, Benutzerobjekte, Zeiger, Autos, Elefanten, es übernimmt nur das Speichern und Abrufen der Daten.

Geschäftslogik Auf der anderen Seite definieren Sie, wie Ihre Datenstrukturen miteinander interagieren. Hier werden Berechtigungen definiert, wo Sie angeben, dass eine Person, die einen Blog-Beitrag erstellt hat, diesen bearbeiten darf und niemand anderes darf dies.

Dies sind zwei grundlegend unterschiedliche Ansichten Ihrer Anwendung und sollten nicht vermischt werden. Sie können Ihre Berechtigungen in einer anderen Datenstruktur speichern (als Array von ganzen Zahlen oder a Hash-tabelle von Berechtigungsobjekten zum Beispiel – oder jede andere Datenstruktur) und Sie können andere Flags in Ihrer BitField-Datenstruktur speichern (wie boolesche Einstellungen Ihrer Benutzer, wie “möchte Newsletter erhalten” oder “E-Mail-Adresse wurde verifiziert”).

Eine weitere Verbesserung ist die Verwendung von Hex-Werten für diese Konstanten, dies stellt sicher, dass Ihr 16. Wert noch lesbar ist. (Ich würde eher empfehlen, Bit-Shift-Operatoren in den Konstanten-Deklarationen zu verwenden, was noch besser lesbar ist, aber dies ist mit dem aktuellen PHP-Interpreter aus Performance-Gründen nicht möglich.)

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);

Dieselbe Klasse ohne hexadezimale Werte ist weniger lesbar, insbesondere wenn Sie weitere Flags hinzufügen:

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;
}

Ich würde weiter definieren, dass der Parameter für set() eine Single-Bit-Ganzzahl sein muss und keine Flag-Nummer. Die set()-Implementierung von demon ist das, was ich meine:

$this->value |= $n;

“Ich mag die Idee, es erweiterbarer / allgemeiner zu machen, damit verschiedene Klassen dies erweitern und für verschiedene Abschnitte verwenden können. Ich bin mir nur noch nicht sicher, wie das geht.”

Tun Sie das nicht, dafür gibt es verschiedene Gründe. In keiner bestimmten Reihenfolge und nur kurz: Funktionsklassen von Datenobjekten trennen. Verlängere nicht, was keine Vererbung braucht. Verwenden Sie stattdessen eine Eigenschaft. Die Erweiterungsklassen müssen normalerweise nicht eng mit der Bitmaskenklasse gekoppelt sein, um überhaupt zu funktionieren. Außerdem kann man in PHP nur von einer Klasse aus erweitern. Wenn Sie dies für eine so begrenzte Verwendung verwenden, haben Erweiterungsobjekte diese Funktion bereits gebrannt.

Wahrscheinlich lieben Sie es also, keine binären Berechnungen in Ihrem Gehirn durchführen zu müssen, sondern stattdessen eine Klasse zu haben, die die binäre Berechnung für Sie gekapselt hat und die eine menschlichere Schnittstelle bietet (zumindest Namen statt Zahlen), mit der Sie interagieren können. Bußgeld. Aber das ist es eben. Sie können die Bitmaske weitergeben, indem Sie den Binärwert übergeben. Wenn Sie keine Binärwerte benötigen, aufzählen Klasse stattdessen könnte das sein, wonach Sie bereits suchen (überprüfen Sie im Besonderen FlagsEnum).

.

304320cookie-checkDiese PHP-Bitfeldklasse für Einstellungen/Berechtigungen verbessern?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy