Domanda

Ho cercato di capire il modo migliore per utilizzare Bitmask o Bitfields in PHP da molto tempo per diverse aree della mia applicazione per diverse impostazioni e autorizzazioni utente. Il più lontano che sono arrivato così lontano è da una classe fornita da Svens nel post di Overflow dello stack Bitmask in PHP per le impostazioni?. L'ho leggermente modificato di seguito, cambiandolo per utilizzare le costanti di classe invece di definire e assicurarmi che il metodo GET venga superato solo un INT. Ho anche del codice di esempio per testare la funzionalità della classe di seguito.

Sto cercando eventuali suggerimenti/codice per migliorare ancora di più questa classe in modo che possa essere utilizzata nella mia applicazione per le impostazioni e in alcuni casi le autorizzazioni utente.

Risposta nel commento qui sotto da McRumley

Inoltre, ho una domanda sulla numerazione delle mie costanti. In altre classi e campione di codice per questo tipo avrà cose elencate in poteri di 2. Tuttavia, sembra funzionare come per quanto posso dire anche se numerai le mie costanti 1,2,3,4,5,6 Invece di 1, 2, 4, 8, 16, ecc. Quindi qualcuno può anche chiarire se dovrei cambiare le mie costanti?


Alcune idee ... Vorrei davvero trovare un modo per estendere questa classe in modo che sia facile da usare con altre classi. Diciamo che ho un User classe e a Messages classe. Entrambi i User e Messages La classe estenderà questa classe e sarà in grado di utilizzare la maschera bit per le loro impostazioni/autorizzazioni (insieme ad altre classi in seguito). Quindi forse le costanti di classe attuale dovrebbero essere cambiate in modo che possano essere passate o qualche altra opzione? Preferirei davvero non dover definire (definire ('perm_read', 1);) in altre parti del sito/script e vorrei mantenerlo in qualche modo incapsulato, ma anche flessibile; Sono aperto alle idee. Voglio che questo sia solido e flessibile come ho detto di utilizzare con più altre classi per impostazioni o autorizzazioni. Forse dovrebbe essere usato una sorta di array? @Svens dalla mia domanda precedente collegata sopra ha pubblicato un commento con "Implementa alcuni getter/setter automobilistici o arrayaccess per un ulteriore sorprendente. - Svens" Cosa ne pensi anche di qualcosa del genere?

Includi il codice sorgente di esempio, se possibile, per favore.

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

Esempio di utilizzo ...

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

Soluzione

Altri hanno contribuito a spiegare ulteriormente il bit di mascheramento di questo, quindi mi concentrerò

"Mi piace l'idea di renderlo più estensibile/generico in modo che classi diverse possano estenderlo e usarlo per sezioni diverse, non sono sicuro di come farlo"

Dal tuo commento sul post di @Charles.

Come ha giustamente detto Charles, puoi riutilizzare la funzionalità della tua classe Bitmask estraendo la funzionalità in una classe astratta e mettendo le "impostazioni" effettive (in questo caso autorizzazioni) in classi concrete derivate.

Per esempio:

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

E poi l'utilizzo diventa semplicemente:

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

E per impostare le impostazioni sulla privacy, basta istanziare un nuovo oggetto UserPrivaCystings_bitfield e utilizzare invece.

In questo modo, puoi creare tanti diversi set di oggetti bitfield che l'applicazione richiede semplicemente definendo un insieme di costanti che rappresentano le tue opzioni.

Spero che questo sia di qualche utilità per te, ma in caso contrario, forse sarà di qualche utilità per qualcun altro che legge questo.

Altri suggerimenti

In altre classi e campione di codice per questo tipo avrà le cose elencate in poteri di 2, tuttavia sembra funzionare come per quanto posso dire anche se numerai le mie costanti 1,2,3,4,5,6 anziché 1,2,4,8,16 ecc. Quindi qualcuno può anche chiarire se dovrei cambiare le mie costanti?

Non è necessario, perché il codice si sta già prendendo cura di questo. Questa spiegazione sarà un po 'rotonda.

Il motivo per cui i campi bit vengono gestiti come poteri di due è che ogni potenza di due è rappresentata da un singolo bit. Questi singoli bit possono essere uniti insieme in un singolo intero che può essere passato. Nelle lingue di livello inferiore, è "più facile" passare un numero rispetto a, diciamo, una struttura.

Vorrei dimostrare come funziona. Impostiamo alcune autorizzazioni usando i poteri di due:

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

Ispezioniamo i valori di bit di queste autorizzazioni nel prompt interattivo 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

Ora creiamo un utente con accesso a lettura e accesso alla scrittura.

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

O un utente che può leggere, scrivere, eliminare, ma non modificare:

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

Possiamo controllare l'autorizzazione usando bitwise e e assicurarci che il risultato non sia zero:

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)

(Vale la pena notarlo PERM_NONE & PERM_NONE è 0 & 0, che è zero. L'autorizzazione "nessuna" che ho creato non funziona davvero qui e può essere prontamente dimenticato.)

La tua classe sta facendo qualcosa leggermente diverso, ma il risultato finale è identico. Sta usando un bit spostamento per spostare un bit "su" a sinistra x tempi, dove x è il numero del permesso. In effetti, Questo sta aumentando 2 al potere del valore dell'autorizzazione. Una dimostrazione:

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

Mentre questi metodi sono effettivamente identico, direi che il semplice anding e l'oring è più facile da leggere rispetto a Xoring e Bit shifting.

Sto cercando eventuali suggerimenti/codice per migliorare ancora di più questa classe in modo che possa essere utilizzata nella mia app per le impostazioni e in alcuni casi le autorizzazioni utente.

Ho un suggerimento e un avvertimento.

Il mio suggerimento sarebbe fare la classe astratto e non definire alcuna autorizzazione al suo interno. Invece, costruisci classi che ereditano da esso e definiscono le proprie autorizzazioni. Non vuoi prendere in considerazione la condivisione della stessa autorizzazione nomi In campi bit non correlati e prefissarli con nomi di classe è piuttosto sano. Mi aspetto che lo avresti fatto comunque.

Il mio avvertimento è semplice ma terribile: PHP non può rappresentare in modo affidabile un numero intero maggiore di 31 bit. In effetti, può rappresentare numeri interi a 63 bit solo quando viene compilato su un sistema a 64 bit. Ciò significa che, se stai distribuendo la tua domanda al pubblico in generale, lo sarai limitato a non più di 31 autorizzazioni Se desideri utilizzare le funzioni matematiche integrate.

L'estensione GMP Include operazioni bitwise che possono funzionare su numeri interi arbitrari.

Un'altra opzione potrebbe essere Utilizzo del codice da questa risposta su grandi numeri interi, che potrebbe permetterti di rappresentare un numero intero enorme come stringa, anche se fare le operazioni bitwise su questo potrebbe essere ... interessante. (Potresti convertirlo in base a Base-2, quindi eseguire un substr per la stringa "1" o "0" nella posizione prevista, ma sarà una grande resistenza alle prestazioni.)

Ecco la mia proposta:

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

}
?>

Come puoi vedere, ho usato 1, 2, 4, 8, ecc. (Poteri di 2) per semplificare i calcoli. Se mappini un permesso a un po 'che hai:

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

Quindi è possibile utilizzare le operazioni logiche, ad esempio lo hai inizialmente:

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

Se si desidera aggiungere autorizzazioni per scrivere, è necessario utilizzare solo il bit o l'operatore:

    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

Per rimuovere un bit devi usare $ value & ~ $ bit, ad esempio rimuovere il bit di scrittura:

    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

Infine, se si desidera testare se un bit è abilitato l'operazione è necessario e $ valore rispetto al perm_xxx che si desidera testare:

    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

Se il risultato non è zero, hai l'autorizzazione, altrimenti non lo fai.

L'errore più grande che vedo nella tua classe è che stai mescolando la logica aziendale in una struttura di dati. Lo scopo della tua classe è archiviare più valori booleani (cioè vero/falso) in un singolo numero intero. Questo non lo fa avere da fare in una classe, ma è conveniente. E questo è il suo scopo.

Lancerei le flag di autorizzazione in classe e le esternalizzerei nelle lezioni di logica aziendale.

u003CEDIT>

UN struttura dati è un'entità che gestisce una cosa: dati. I dati non sono interpretati in alcun modo. UN pila, Fore Esempio, è una struttura di dati in cui puoi inserire cose, che ti darà prima l'ultimo elemento. Ed ecco il punto: Non importa, quello che hai messo lì dentro: numeri interi, oggetti utente, puntatori, automobili, elefanti, gestirà solo la memorizzazione e il recupero dei dati.

Logica di business D'altra parte è dove definisci come le strutture di dati interagiscono tra loro. È qui che sono definite le autorizzazioni, in cui dichiari che una persona che ha creato un post sul blog può modificarlo e nessun altro è autorizzato.

Queste sono due opinioni fondamentalmente diverse della tua applicazione e non dovrebbero essere contrastanti. È possibile archiviare le autorizzazioni in un'altra struttura di dati (come una matrice di numeri interi o un tavolo hash di oggetti di autorizzazione, ad esempio - o Qualsiasi altra struttura di dati) e puoi archiviare altre flag nella struttura dei dati Bitfield (come le preferenze booleane dei tuoi utenti, come "desidera ricevere newsletter" o "l'indirizzo e -mail è stato verificato").

u003C/EDIT>

Un altro miglioramento è l'uso dei valori esadecimali per queste costanti, questo garantirà che il tuo sedicesimo valore sia ancora leggibile. (Preferirei consigliare di usare gli operatori di bit-shift nelle costanti dichiarazioni, che è ancora più leggibile, ma ciò non è possibile con l'attuale interprete PHP per motivi di prestazioni.)

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

u003CEDIT>

La stessa classe senza valori esadecimali è meno leggibile, soprattutto se aggiungi più flag:

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

u003C/EDIT>

Direi inoltre che il parametro da set () deve essere un numero intero a bit singolo e non un numero di flag. L'implementazione set () di Demon è ciò che intendo:

$this->value |= $n;

"Mi piace l'idea di renderlo più estensibile/generico in modo che classi diverse possano estenderlo e usarlo per sezioni diverse, non sono sicuro di come farlo"

Non farlo, ci sono vari motivi per cui. In nessun ordine specifico e appena in breve: classi funzionali separate dagli oggetti dati. Non estendere ciò che non ha bisogno di eredità. Utilizzare invece una proprietà, le classi estese normalmente non devono essere strettamente accoppiate con la classe Bitmask per lavorare. Inoltre, in PHP puoi estendersi solo da una classe. Se ne si utilizza per un uso così limitato, gli oggetti estensi hanno già bruciato quella funzione.

Quindi probabilmente ti piace non dover fare calcoli binari nel tuo cervello ma avere una classe invece che ha incapsulato il calcolo binario per te e che offre un'interfaccia più umana (nomi invece di numeri con cui dire almeno) con cui interagire. Bene. Ma è proprio così. Puoi passare lungo la maschera bit passando il valore binario. Se non hai bisogno di valori binari, un enum classe invece Potrebbe essere quello che stai già cercando (controlla il flagsenum in specifico).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top