marc-mabe / php-enum

Simple and fast implementation of enumerations with native PHP
BSD 3-Clause "New" or "Revised" License
464 stars 36 forks source link

Add getter and setter for bitset in EnumSet #50

Closed TuxCoder closed 9 years ago

TuxCoder commented 9 years ago

Hello,

Often it's nessessary to save an EnumSet in a database or somewhere else. My thougt was to only save only the bitset, serilize works also but need a hug amout of space, is not such easy searchable, and i don't think it works well if you upgrade your enum and load an old version from the database.(probably also not work with that solution)

What do you think about a getter and setter method for bitset?

ps. nice enum classes, thx for it.

marc-mabe commented 9 years ago

I like the basic idea to have a getter and setter for the internal bitset.

The issue what I have is that it's currently impossible to have a set of more than 32/64 enumerators because of the integer limitations. I was hoping that this will be abolished by https://wiki.php.net/rfc/bigint but as Andrea Faults has been left the community the RFC was never voted and it's too late for PHP7. So the limitations will exist for at least the next years.

So I'm wrapping my head about other ways solving this like with a bitset based on a binary string but PHP will cast binary strings to integers on left/right shift and the RFC solving this is looks dead. too. (https://wiki.php.net/rfc/string-bitwise-shifts)

Because I don't like to introduce a BC break if I find a good way resolving the integer bitset limitations I'm a bit deliberate on this change.

Do you have any other ideas?

ps. nice enum classes, thx for it.

Thanks for the compliments

TuxCoder commented 9 years ago

I think it's not easy to implement it with more than int.

One way could be to look to the size of the enum an if its bigger than the size of int, use gmp for all operations. (https://php.net/manual/en/book.gmp.php)

The problem with this solution is that a php-extension is required and I don't know about the performance.

marc-mabe commented 9 years ago

I was thinking something like that:

<?php

class EnumSet {

    private $bitSize = 64; // machine depending
    private $setBitFunc = 'setIntegerBit'; // enumeration and machine depending

    // ...

    public function attach($enumerator) {
        $enumeration = $this->enumeration;
        $this->{$this->setBitFunc}($enumeration::get($enumerator)->getOrdinal());
    }
    private function setIntegerBit($ordinal) {
        $this->bitset |= 1 << $ordinal;
    }
    private function setBinaryBit($ordinal) {
        $bit = pack('N', 1 << ($ordinal % $this->bitSize)) . str_repeat("\0", floor($ordinal / $this->bitSize));
        $this->bitset |= $bit;
    }

    // ...
}
TuxCoder commented 9 years ago

my suggestion is to implement it in a external interface/classes, so its easier to test and replace

interface BitSetManipulator{
    /**
      * @param $bitset  mixed   current bitset
      * @param $bit    number of bit to manipulate
      * @return mixed  new bitset
      */
    public static function setBit($bitset,$bit);

    /**
      * @param $bitset  mixed   current bitset
      * @param $bit    number of bit to get
      * @return boolean
      */
    public static function getBit($bitset,$bit);

    /**
      * @param $bitset  mixed   current bitset
      * @param $bit    number of bit to set to false
      * @return mixed  new bitset
      */
    public static function resetBit($bitset,$bit);

    /**
      * @param $bitset  mixed  current bitset
      * @return string    a binary string with the bitset
      */
    public static function saveBitset($bitset);

    /**
      * @param $bitset   string    a binary string with the bitset
      * @param $size   int    nummber of bits to save
      * @return mixed  bitset in class specific format
      */
   public static function loadBitset($bitset,$size);
}

loadBitset(""); could be used to initialize the $bitset

can be implemented as int, array, gmp or otherwise.

one way is also to combine array and int:

public static function setBit($bitset,$bit) {
    $bitset[floor($bit/(PHP_INT_SIZE*8))] |= 1<<$bit%(PHP_INT_SIZE*8);
TuxCoder commented 9 years ago

or use the array/int way only, should be enough and is not so much to implement.

In the getBitset and setBitset, use a foreach and pack them into a binary string.

marc-mabe commented 9 years ago
interface BitSet extends Serializable {
        public function set($bit);
        public function isset($bit);
        public function unset($bit);

        // serialize to binary string / unserialize from binary string
        public function serialize();
        public function unserialize();
}

class IntegerBitSet implements BitSet { /* ... */ }
class BinaryBitSet implements BitSet { /* ... */ }
class GmpBitSet implements BitSet { /* ... */ }

use it in EnumSet:

class EnumSet {
    // ...
    public function __construct(/*...*/) {
        // ...
        if (PHP_INT_SIZE * 8 >= $size) {
            $this->bitset = new IntegerBitSet();
        } else {
            $this->bitset = extension_loaded('gmp') ? new GmpBitSet(); : new BinaryBitSet($size);
        }
    }
    // ...
}

// init from / load into MySQL-SET

$hex = $db->query('SELECT HEX(`col`) FROM t')->fetchColumn(0);
$enumSet->getBitSet()->unserialize(hex2bin($hex));

$hex = bin2hex($enumSet->getBitSet()->serialize());
TuxCoder commented 9 years ago

I made a implementation of an array/int structure and it works well. https://github.com/TuxCoder/php-enum/tree/master-bitset take a look and say what you think of it? I haven't test it yet on 32 bit php, that is needed!

in this implementation you can use it like:

$hex = $db->query('SELECT HEX(`col`) FROM t')->fetchColumn(0);
$enumSet->setBitset(hex2bin($hex));

$hex = bin2hex($enumSet->getBitset());

After i saw what kind of binary function we need, gmp is a kind of overkill for this. And now i think its not necessary to have different backends.

marc-mabe commented 9 years ago

Yea this is a good and simple enough approve :+1: Can you create a new PR for this one so we can discus details better.

Thanks!

marc-mabe commented 9 years ago

Some notes first:

TuxCoder commented 9 years ago

made new pullrequest #51