Pregunta

He estado tratando de descubrir la mejor manera de usar Bitmask o Bitfields en PHP durante mucho tiempo para diferentes áreas de mi aplicación para diferentes configuraciones y permisos de los usuarios. Lo más lejos que he llegado hasta ahora es de una clase aportada por SVENS en el Post de Overflow de Stack Bitmask en PHP para configuraciones?. Lo he modificado ligeramente a continuación, cambiándolo para usar constantes de clase en lugar de definir y asegurarme de que el método GET se pase solo un int. También tengo algún código de muestra para probar la funcionalidad de la clase a continuación.

Estoy buscando cualquier sugerencia/código para mejorar esta clase aún más, por lo que puede usarse en mi aplicación para configuraciones y, en algunos casos, permisos de usuario.

Respondido en el comentario a continuación por Mcrumley

Además, tengo una pregunta sobre la numeración de mis constantes. En otras clases y muestra de código para este tipo, tendrá cosas enumeradas en poderes de 2. Sin embargo, parece funcionar igual por lo que puedo decir, incluso si numo mis constantes 1,2,3,4,5,6 En lugar de 1, 2, 4, 8, 16, etc. Entonces, ¿alguien también puede aclarar si debería cambiar mis constantes?


Algunas ideas ... realmente me gustaría encontrar una manera de extender esta clase para que sea fácil de usar con otras clases. Digamos que tengo un User clase y un Messages clase. Ambos User y Messages La clase extenderá esta clase y podrá usar la masa de bits para su configuración/permisos (junto con otras clases más adelante). Entonces, ¿tal vez las constantes de clase actuales deberían cambiarse para que puedan pasar o alguna otra opción? Realmente preferiría no tener que definir (definir ('Perm_read', 1);) en otras partes del sitio/script y me gustaría mantenerlo algo encapsulado, pero flexible también; Estoy abierto a ideas. Quiero que esto sea sólido y flexible como dije que usara con varias otras clases para configuraciones o permisos. ¿Posiblemente se debe usar algún tipo de matriz? @Svens de mi pregunta anterior vinculada anteriormente publicó un comentario con "Implementar algunos getters/setters o arreyaccess de Automagic para una increíble asombro. - Svens" ¿Qué piensas de algo así también?

Incluya el código fuente de ejemplo si es posible, por favor.

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

Ejemplo de uso ...

<?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>';
    }
?>
¿Fue útil?

Solución

Otros han ayudado a explicar más a fondo el enmascaramiento de esto, así que me concentraré en

"Me gusta la idea de hacerlo más extensible/genérico para que diferentes clases puedan extender esto y usarlo para diferentes secciones, simplemente no estoy seguro de cómo hacerlo todavía"

De su comentario en la publicación de @Charles.

Como dijo Charles con razón, puede reutilizar la funcionalidad de su clase de masas de bits extrayendo la funcionalidad en una clase abstracta y colocando la "configuración" real (en este caso permisos) en clases concretas derivadas.

Por ejemplo:

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

Y luego el uso simplemente se convierte en:

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

Y para establecer la configuración de privacidad, solo instancia un nuevo objeto UserPrivacySettings_Bitfield y usa eso en su lugar.

De esta manera, puede crear tantos conjuntos diferentes de objetos Bitfield como lo requiere su aplicación simplemente definiendo un conjunto de constantes que representan sus opciones.

Espero que esto sea de alguna utilidad para usted, pero si no, tal vez sea de alguna utilidad para alguien más que lea esto.

Otros consejos

En otras clases y muestra de código para este tipo, tendrá cosas enumeradas en los poderes de 2, sin embargo, parece funcionar igual por lo que puedo decir, incluso si numero mis constantes 1,2,3,4,5,6 en lugar de 1,2,4,8,16 etc. Entonces, ¿alguien puede aclarar si debería cambiar mis constantes?

No es necesario, porque el código ya se está ocupando de eso. Esta explicación será un poco indirecta.

La razón por la que los campos de bit se manejan como poderes de dos es que cada poder de dos está representado por un solo bit. Estos bits individuales se pueden orientar bit a bit juntos en un solo entero que se puede pasar. En idiomas de nivel inferior, es "más fácil" pasar por un número que, por ejemplo, una estructura.

Déjame demostrar cómo funciona esto. Establecamos algunos permisos utilizando los poderes de dos:

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

Inspeccionemos los valores de bits de estos permisos en el mensaje PHP Interactive:

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

Ahora creemos un usuario que tenga acceso de lectura y acceso de escritura.

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

O un usuario que pueda leer, escribir, eliminar, pero no editar:

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

Podemos verificar el permiso usando bitwise y y asegurarnos de que el resultado no sea cero:

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 señalar que PERM_NONE & PERM_NONE es 0 & 0, que es cero. El permiso "Ninguno" que creé en realidad no funciona aquí, y se puede olvidar rápidamente).

Tu clase está haciendo algo ligeramente diferente, pero el resultado final es idéntico. Está usando el cambio de bit para mover un bit "On" hacia la izquierda X Times, donde X es el número de permiso. En efecto, Esto está elevando 2 al poder del valor del permiso. Una demostración:

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

Mientras que estos métodos son efectivamente Idéntico, argumentaría que simple y oración es más fácil de leer que el xoring y el cambio de bits.

Estoy buscando cualquier sugerencia/código para mejorar esta clase aún más, por lo que puede usarse en mi aplicación para la configuración y, en algunos casos, los permisos de los usuarios.

Tengo una sugerencia y una advertencia.

Mi sugerencia sería hacer la clase resumen y no definir ningún permiso dentro de él. En cambio, cree clases que hereden de él y definan sus propios permisos. No quieres considerar compartir el mismo permiso nombramiento En campos de bits no relacionados, y prefijarlos con nombres de clases es bastante sano. Espero que lo hicieras de todos modos.

Mi advertencia es simple pero grave: PHP no puede representar de manera confiable un entero mayor de 31 bits. De hecho, solo puede representar enteros de 63 bits cuando se compila en un sistema de 64 bits. Esto significa que, si está distribuyendo su aplicación al público en general, será restringido a no más de 31 permisos Si desea utilizar las funciones matemáticas incorporadas.

La extensión GMP Incluye operaciones bit a bit que pueden funcionar en enteros de longitud arbitraria.

Otra opción podría ser Usar el código de esta respuesta en enteros grandes, lo que podría permitirle representar un enorme entero como una cadena, aunque hacer operaciones bitwise en eso podría ser ... interesante. (Puede convertirlo en la base-2, luego hacer una verificación de substr para la cadena "1" o "0" en la ubicación esperada, pero eso será un gran arrastre de rendimiento).

Aquí está mi propuesta:

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

}
?>

Como puede ver, utilicé 1, 2, 4, 8, etc. (potencias de 2) para simplificar los cálculos. Si asigna un permiso a un bit que tiene:

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

Luego puede usar operaciones lógicas, por ejemplo, tiene esto inicialmente:

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

Si desea agregar permisos para escribir, solo necesita usar el bitwise u operador:

    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

Para eliminar un bit debe usar $ valor $ y ~ $ bit, por ejemplo, eliminar el bit de escritura:

    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

Finalmente, si desea probar si un bit está habilitado la operación que tiene que hacer y $ valor contra el Perm_xxx que desea probar:

    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

Si el resultado no es cero, tiene el permiso, de lo contrario no.

El mayor error que veo en su clase es que está mezclando la lógica de negocios en una estructura de datos. El propósito de su clase es almacenar múltiples valores booleanos (es decir, verdadero/falso) en un solo número entero. Esto no tener para hacerse en una clase, pero es conveniente. Y ese es su propósito.

Dejaría las banderas de permiso en la clase y las subcontrataría en sus clases de lógica de negocios.

u003CEDIT>

A estructura de datos es una entidad que maneja una cosa: datos. Los datos no se interpretan de ninguna manera. A pila, El ejemplo, es una estructura de datos en la que puede poner cosas, que le dará primero el último elemento. Y aquí está el punto: No le importa, lo que pones allí: enteros, objetos de usuario, punteros, automóviles, elefantes, solo manejará el almacenamiento y la recuperación de los datos.

Lógica de negocios Por otro lado, es donde define cómo interactúan sus estructuras de datos entre sí. Aquí es donde se definen los permisos, donde declara que una persona que creó una publicación de blog puede editarlo, y nadie más puede hacerlo.

Estas son dos vistas fundamentalmente diferentes de su aplicación y no deben ser mixtas. Puede almacenar sus permisos en otra estructura de datos (como una variedad de enteros o un tabla de picadillo de objetos de permiso, por ejemplo, o Cualquier otra estructura de datos) y puede almacenar otras banderas en su estructura de datos de Bitfield (como las preferencias booleanas de sus usuarios, como "quiere recibir boletín" o "Se verificó la dirección de correo electrónico").

u003C/EDIT>

Otra mejora es el uso de los valores hexadecimales para estas constantes, esto asegurará que su valor 16 aún sea legible. (Prefiero recomendar usar operadores de cambio de bits en las declaraciones constantes, que es aún más legible, pero esto no es posible con el intérprete PHP actual por razones de rendimiento).

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 misma clase sin valores hexadecimales es menos legible, especialmente si agrega más banderas:

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>

Definiría además que el parámetro para establecer () debe ser un entero de un solo bit, y no un número de bandera. La implementación set () de Demon es lo que quiero decir:

$this->value |= $n;

"Me gusta la idea de hacerlo más extensible/genérico para que diferentes clases puedan extender esto y usarlo para diferentes secciones, simplemente no estoy seguro de cómo hacerlo todavía"

No hagas eso, hay varias razones por las cuales. En ningún orden específico y en resumen: clases funcionales separadas de los objetos de datos. No extienda lo que no necesita herencia. Use una propiedad en su lugar, las clases de extensión normalmente no necesitan estar estrechamente acopladas con la clase de masterías de bits para funcionar en absoluto. Además, en PHP solo puede extenderse desde una clase. Si hace uso de eso para un uso tan limitado, extender los objetos ya han quemado esa función.

Por lo tanto, probablemente le encanta no necesitar hacer cálculos binarios en su cerebro, pero tiene una clase que ha encapsulado el cálculo binario para usted y que ofrezca una interfaz que sea más humana (nombres en lugar de números para decir al menos) para interactuar. Multa. Pero eso es todo. Puede pasar por la masa de bits pasando el valor binario. Si no necesita valores binarios, un enumer clase en su lugar Puede ser lo que ya está buscando (verifique el flagsenum en específico).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top