Pregunta

Bits y Bitmask son algo que he estado luchando por entender por un tiempo, pero me gustaría aprender a usarlos para configuraciones y cosas así en PHP.

Finalmente he encontrado una clase que afirma hacer exactamente eso, y como puedo decir, parece funcionar, pero no estoy seguro de si es la mejor manera de hacerlo. Publicaré el archivo de clase con el código de ejemplo a continuación para mostrarlo en funcionamiento.

Por favor, si tiene experiencia, dígame si se puede mejorar, para el rendimiento o cualquier otra cosa. Realmente quiero aprender esto, y he estado leyendo sobre ello, pero es difícil para mí comprender hasta ahora.

La clase...

<?php
    class bitmask
    {
        /**
         * This array is used to represent the users permission in usable format.
         *
         * You can change remove or add valuesto suit your needs.
         * Just ensure that each element defaults to false. Once you have started storing
         * users permsisions a change to the order of this array will cause the
         * permissions to be incorectly interpreted.
         *
         * @type Associtive array
         */
        public $permissions = array(
                                    "read" => false,
                                    "write" => false,
                                    "delete" => false,
                                    "change_permissions" => false,
                                    "admin" => false
                                    );

        /**
         * This function will use an integer bitmask (as created by toBitmask())
         * to populate the class vaiable
         * $this->permissions with the users permissions as boolean values.
         * @param int $bitmask an integer representation of the users permisions.
         * This integer is created by toBitmask();
         *
         * @return an associatve array with the users permissions.
         */
        public function getPermissions($bitMask = 0)
        {
            $i = 0;
            foreach ($this->permissions as $key => $value)
            {
                $this->permissions[$key] = (($bitMask & pow(2, $i)) != 0) ? true : false;

                // Uncomment the next line if you would like to see what is happening.
                //echo $key . " i= ".strval($i)." power=" . strval(pow(2,$i)). "bitwise & = " . strval($bitMask & pow(2,$i))."<br>";
                $i++;
            }
            return $this->permissions;
        }

        /**
         * This function will create and return and integer bitmask based on the permission values set in
         * the class variable $permissions. To use you would want to set the fields in $permissions to true for the permissions you want to grant.
         * Then call toBitmask() and store the integer value.  Later you can pass that integer into getPermissions() to convert it back to an assoicative
         * array.
         *
         * @return int an integer bitmask represeting the users permission set.
         */
        function toBitmask()
        {
            $bitmask = 0;
            $i = 0;
            foreach ($this->permissions as $key => $value)
            {

                if ($value)
                {
                    $bitmask += pow(2, $i);
                }
                $i++;
            }
            return $bitmask;
        }
    }
?>

¿Cómo configuro/guardo los permisos como un valor de mastería?

<?php
    /**
     * Example usage
     * initiate new bitmask object
     */
    $perms = new bitmask();

    /**
     * How to set permissions for a user
     */
    $perms->permissions["read"] = true;
    $perms->permissions["write"] = true;
    $perms->permissions["delete"] = true;
    $perms->permissions["change_permissions"] = true;
    $perms->permissions["admin"] = false;

    // Converts to bitmask value to store in database or wherever
    $bitmask = $perms->toBitmask();  //in this example it is 15
    $sql = "insert into user_permissions (userid,permission) values(1,$bitmask)";
    echo $sql; //you would then execute code to insert your sql.
?>

Ejemplo de tomar el valor de Bitmask y devolver un verdadero/falso para cada elemento de matriz basado en el valor de bit ...

<?php
    /**
     * Example usage to get the bitmask value from database or session/cache.... then put it to use.
     * $permarr returns an array with true/false for each array value based on the bit value
     */
    $permarr = $perms->getPermissions($bitmask);

    if ($permarr["read"])
    {
        echo 'user can read: <font color="green">TRUE</font>';
    }
    else {
        echo 'user can read: <font color="red">FALSE</font>';
    }

    //user can WRITE permission
    if ($permarr["write"])
    {
        echo '<br>user can write: <font color="green">TRUE</font>';
    }
    else {
        echo '<br>user can write: <font color="red">FALSE</font>';
    }
?>
¿Fue útil?

Solución

Los campos de bits son una herramienta muy útil y eficiente para tratar con banderas o cualquier conjunto de valores booleanos en general.

Para entenderlos, primero necesita saber cómo funcionan los números binarios. Después de eso, debes revisar las entradas manuales en operadores de bit a bit Y asegúrese de saber cómo funciona un bitwise y, y y el cambio izquierdo/derecho.

Un bit que el campo no es más que un valor entero. Supongamos que el tamaño de nuestro campo de bits es fijo y solo un byte. Las computadoras funcionan con números binarios, por lo que si el valor de nuestro número es 29, realmente encontrarás 0001 1101 en la memoria.

Usando bitwise y ((&) y bitwise o (|) Puede leer y establecer cada bit del número individualmente. Ambos toman dos enteros como entrada y realizan una y/o en cada bit individualmente.

Para leer el primer bit de su número, podría hacer algo como esto:

  0001 1101 (=29, our number)
& 0000 0001 (=1, bit mask)
= 0000 0001 (=1, result)

Como puede ver, necesita un número especial en el que solo se establezca el bit que estamos interesados, esa es la llamada "máscara de bits". En nuestro caso es 1. Para leer el segundo bit, tenemos que "empujar" el que está en el dígito de Bitmask a la izquierda. Podemos hacer eso con el operador de cambio de izquierda ($number << 1) o multiplicando nuestro por dos.

  0001 1101
& 0000 0010
= 0000 0000 (=0, result) 

Puedes hacer eso por cada bit en nuestro número. El binario y de nuestro número y la máscara de bits conducen a cero, lo que significa que el bit no fue "establecido", o a un entero no distinto de cero, lo que significa que el bit se estableció.

Si desea establecer uno de los bits, puede usar bitwise o:

  0001 1101
| 0010 0000 (=32, bit mask)
= 0011 1101 (=29+32)

Sin embargo, tendrás que ir de una manera diferente cuando quieras "despejar" un poco.

Un enfoque más general sería:

// To get bit n
$bit_n = ($number & (1 << $n)) != 0
// Alternative
$bit_n = ($number & (1 << $n)) >> $n

// Set bit n of number to new_bit
$number = ($number & ~(1 << $n)) | ($new_bit << $n)

Al principio puede parecer un poco críptico, pero en realidad es bastante fácil.

A estas alturas, probablemente descubres que los campos de bits son una técnica de bajo nivel. Es por eso que recomiendo no usarlos dentro de PHP o bases de datos. Si desea tener un montón de banderas, probablemente esté bien, pero para cualquier otra cosa que realmente no las necesite.

La clase que publicaste me parece un poco especial. Por ejemplo, cosas como ... ? true : false son malas prácticas de la vela. Si desea usar campos de bits, probablemente sea mejor definir algunas constantes y usar el método descrito anteriormente. No es difícil encontrar una clase simple.

define('PERM_READ', 0);
define('PERM_WRITE', 1);

class BitField {
    private $value;

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

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

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

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


$bf = new BitField($user->permissions);

if ($bf->get(PERM_READ)) {
    // can read
}

$bf->set(PERM_WRITE, true);
$user->permissions = $bf->getValue();
$user->save();

No probé ningún código de esta respuesta, pero debería hacer que comience incluso si no está funcionando fuera de la caja.

Tenga en cuenta que está limitado a 32 valores por campo de bits.

Otros consejos

Aquí le mostramos cómo definir los maestros de bits.

// the first mask.  In binary, it's 00000001
define('BITWISE_MASK_1', 1 << 0); // 1 << 0 is the same as 1

// the second mask.  In binary, it's 00000010
define('BITWISE_MASK_2', 1 << 1);

// the third mask.  In binary, it's 00000100
define('BITWISE_MASK_3', 1 << 2);

Para verificar si hay una masa de bits presente (en este caso en un argumento de función), use el bitwise y el operador.

function computeMasks($masks) {
    $masksPresent = array();
    if ($masks & BITWISE_MASK_1)
        $masksPresent[] = 'BITWISE_MASK_1';
    if ($masks & BITWISE_MASK_2)
        $masksPresent[] = 'BITWISE_MASK_2';
    if ($masks & BITWISE_MASK_3)
        $masksPresent[] = 'BITWISE_MASK_3';
    return implode(' and ', $masksPresent);
}

Esto funciona porque cuando usted o dos bytes (digamos, 00000001 y 00010000), Consegues los dos juntos: 00010001. Si usted y el resultado y la máscara original (00010001 y decir, 00000001), obtienes una máscara si está presente (en este caso 00000001). De lo contrario, obtienes cero.

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