Skip to content

reinder83/binary-flags

Repository files navigation

test

BinaryFlags

With this class you can easily add flags to your projects.

The number of flags you can use is limited to the architecture of your system, e.g.: 32 flags on a 32-bit system or 64 flags on 64-bit system. To store 64-bit flags in a database, you will need to store it as UNSIGNED BIGINT in MySQL or an equivalent in your datastore.

This package also comes with a trait which you can use to implement binary flags directly in your own class.

Trait naming

For new code, prefer Reinder83\BinaryFlags\Traits\InteractsWithNumericFlags. Reinder83\BinaryFlags\Traits\BinaryFlags remains available for backward compatibility. For enum-based usage, use Reinder83\BinaryFlags\BinaryEnumFlags (which uses Traits\InteractsWithEnumFlags).

Installing

To install this package simply run the following command in the root of your project.

composer require reinder83/binary-flags

Deprecation Notice (Upcoming v3.0.0 Breaking Change)

Starting in v2.1.0, passing float values as masks or flags is deprecated.

  • Current v2.x behavior: floats are still accepted for backward compatibility, but trigger a deprecation warning.
  • v3.0.0 behavior: masks and flags will be int-only.
  • v3.0.0 behavior: Bits::BIT_64 will be removed.

BIT_64 Notice

Bits::BIT_64 is being removed because PHP numbers for bitwise flags are signed. The 64th bit is the sign bit, so it cannot be used reliably as a normal flag.

Using integer-compatible bits (BIT_1 through BIT_63) prevents these issues and is the supported path for v3.0.0.

To prepare for v3.0.0, cast incoming values before using the API:

$flags->setMask((int) $maskFromLegacySource);
$flags->addFlag((int) $incomingFlag);

See UPGRADE-v3.md for migration details.

Methods

The following methods can be used:

setMask(int $mask)

Overwrite the current mask. This can be passed as first argument in the constructor.

getMask(): int

Retrieve the current mask.

When using BinaryEnumFlags, getMask() returns a Mask object instead. Use getMaskValue(): int on enum-based flags if you need the numeric mask.

getMaskValue(): int

Since: v2.1.0
Returns the numeric mask value for storage/interoperability. This method is only available on enum-backed flags (BinaryEnumFlags).

setOnModifyCallback(callable $onModify)

Set a callback function which is called when the mask changes. This can be passed as second argument in the constructor.

getFlagNames([int $mask, [bool $asArray=false]])

Give the name(s) for the given $mask or the current mask when omitted. When $asArray is true the method will return an array with the names, otherwise a comma separated string will be returned (default).

addFlag(int $flag)

Adds one or multiple flags to the current mask.

removeFlag(int $flag)

Removes one or multiple flags from the current mask.

checkFlag(int $flag, [bool $checkAll=true]): bool

Check if given flag(s) are set in the current mask. By default, it will check all bits in the given flag. When you want to match any of the given flags set $checkAll to false.

checkAnyFlag(int $mask): bool

Since: v1.0.1
For your convenience I've added an alias to checkFlag with $checkAll set to false.

count(): int

Since: v1.2.0
Returns the number of flags that have been set.

jsonSerialize(): mixed

Since: v1.2.0
Return a value that can be encoded by json_encode() in the form of ["mask" => 7]. You should not have to call this method directly, instead you can pass the BinaryFlags object to json_encode which will convert it to '{"mask": 7}'.

Static Methods

The following static methods can be used:

getAllFlags(): array

Since: v1.1.0
Return all the flags with their names as an array, using their flag mask as key. This method can also be overloaded to return custom names for the flags, which will be used by the getFlagNames method.

getAllFlagsMask(): int

Since: v1.1.0
Return mask of all the flags together

Iteration

Since: v1.2.0
You can treat a BinaryFlags object as an iterable, where each iteration will return the next bit value that has been set including its description (or the name of the constant representing the bit value).

Example usage

Below is some example usage code

Create classes
// example classes which the following examples will refer to
use Reinder83\BinaryFlags\BinaryFlags;
use Reinder83\BinaryFlags\Bits;

class ExampleFlags extends BinaryFlags
{
    const FOO = Bits::BIT_1;
    const BAR = Bits::BIT_2;
    const BAZ = Bits::BIT_3;
}
Simple usage
$exampleFlags = new ExampleFlags();

// add BAR flag
$exampleFlags->addFlag(ExampleFlags::BAR);

var_export($exampleFlags->checkFlag(ExampleFlags::FOO)); 
// false
var_export($exampleFlags->checkFlag(ExampleFlags::BAR)); 
// true

// remove BAR flag
$exampleFlags->removeFlag(ExampleFlags::BAR);

var_export($exampleFlags->checkFlag(ExampleFlags::BAR)); 
// false
Usage with multiple flags
$exampleFlags = new ExampleFlags();

// add FOO and BAR
$exampleFlags->addFlag(ExampleFlags::FOO | ExampleFlags::BAR); 

var_export($exampleFlags->checkFlag(ExampleFlags::FOO)); 
// true

var_export($exampleFlags->checkFlag(ExampleFlags::FOO | ExampleFlags::BAZ)); 
// false because BAZ is not set

var_export($exampleFlags->checkFlag(ExampleFlags::FOO | ExampleFlags::BAR)); 
// true because both flags are set

var_export($exampleFlags->checkFlag(ExampleFlags::FOO | ExampleFlags::BAZ, false)); 
// true because one of the flags is set (FOO)

// alias of the above method
var_export($exampleFlags->checkAnyFlag(ExampleFlags::FOO | ExampleFlags::BAZ)); 
// true
Enum usage (optional)
use Reinder83\BinaryFlags\BinaryEnumFlags;
use Reinder83\BinaryFlags\Mask;

enum Permission: int
{
    case CanView = Bits::BIT_1;
    case CanBook = Bits::BIT_2;
    case CanCancel = Bits::BIT_3;
}

class PermissionFlags extends BinaryEnumFlags
{
    protected static function getFlagEnumClass(): string
    {
        return Permission::class;
    }
}

$flags = new PermissionFlags(Permission::CanView);
$flags->addFlag(Permission::CanBook);
$flags->addFlag(Mask::forEnum(Permission::class, Permission::CanCancel));

var_export($flags->checkFlag(Permission::CanBook));
// true

var_export($flags->getFlagNames());
// 'Can View, Can Book, Can Cancel'
Migrating from numeric flags to enum flags
// Before (numeric PermissionFlags)
use Reinder83\BinaryFlags\BinaryFlags;

class PermissionFlags extends BinaryFlags
{
    public const CAN_VIEW = Bits::BIT_1;
    public const CAN_BOOK = Bits::BIT_2;
}

$flags = new PermissionFlags($storedMask);
$flags->addFlag(PermissionFlags::CAN_VIEW | PermissionFlags::CAN_BOOK);
$storedMask = $flags->getMask(); // int

// After (enum PermissionFlags)
use Reinder83\BinaryFlags\BinaryEnumFlags;
use Reinder83\BinaryFlags\Mask;

enum Permission: int
{
    case CanView = Bits::BIT_1;
    case CanBook = Bits::BIT_2;
}

class PermissionFlags extends BinaryEnumFlags
{
    protected static function getFlagEnumClass(): string
    {
        return Permission::class;
    }
}

$flags = new PermissionFlags(Mask::fromInt($storedMask, Permission::class));
$flags->addFlag(Permission::CanView);
$flags->addFlag(Permission::CanBook);

// Save as integer for storage/interop
$storedMask = $flags->getMaskValue();
Flag names example

By default, the flag names are based on the constant names

$exampleFlags = new ExampleFlags();

$exampleFlags->addFlag(ExampleFlags::FOO | ExampleFlags::BAR | ExampleFlags::BAZ);
var_export($exampleFlags->getFlagNames());
// 'Foo, Bar, Baz'

// null will force current mask
var_export($exampleFlags->getFlagNames(null, true));
/*
array (
  0 => 'Foo',
  1 => 'Bar',
  2 => 'Baz',
)
*/

// get flag names of given mask
var_export($exampleFlags->getFlagNames(ExampleFlags::FOO | ExampleFlags::BAR));
// 'Foo, Bar'
Custom flag names example

If you want custom flag names that are not equal to the constant names, you can override these with getAllFlags()

class ExampleFlagsWithNames extends BinaryFlags
{
    const FOO = Bits::BIT_1;
    const BAR = Bits::BIT_2;
    const BAZ = Bits::BIT_3;
    
    public static function getAllFlags()
    {
        return [
            static::FOO => 'My foo description',
            static::BAR => 'My bar description',
            static::BAZ => 'My baz description',
        ];
    }
}

$exampleFlags = new ExampleFlagsWithNames();

$exampleFlags->addFlag(ExampleFlags::FOO | ExampleFlags::BAR | ExampleFlags::BAZ);

// null will force current mask
var_export($exampleFlags->getFlagNames(null, true));
/*
array (
  0 => 'My foo description',
  1 => 'My bar description',
  2 => 'My baz description',
)
*/
Example usage with Eloquent models
use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
    private $flagsObject;

    /**
     * Retrieve flags
     * @return ExampleFlags
     */
    public function getFlagsAttribute()
    {
        if ($this->flagsObject === null) {
            $this->flagsObject = new ExampleFlags(
                $this->attributes['flags'], // set current flags mask
                function (ExampleFlags $flags) { // set callback function
                    // update the flags in this model
                    $this->setAttribute('flags', $flags->getMask());
                }
            );
        }
        return $this->flagsObject;
    }
}

// retrieve object from DB
$test = Test::find(1);

// do binary operations on the flags class as described earlier
$test->flags->checkFlag(ExampleFlags::FOO);

Support

For bugs or feature requests feel free to contact me or submit an issue or pull request. Or you can support me by buying me a coffee:

Buy me a coffee

About

Simple BinaryFlags class to extend from

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors

Languages