Looking to hire Laravel developers? Try LaraJobs

laravel-bitwise-trait maintained by fanmade

Description
Simple trait to use bitwise operators on any class
Last update
2026/01/27 23:36 (dev-dependabot/composer/phpunit/phpunit-9.6.33)
License
Downloads
76 213

Comments
comments powered by Disqus

Laravel Bitwise Trait

Latest Version on Packagist Tests Total Downloads License

A tiny, framework-agnostic trait for storing many boolean flags in a single integer column using bitwise operations. One tinyInteger column can hold up to 8 independent true/false values instead of 8 separate columns.

Although it's built with Laravel in mind, the trait itself has no framework dependency — it works in any PHP class.

Inspired by the PHP manual on bitwise operators and Aaron Francis' write-up on bitmasking in Laravel and MySQL.

Requirements

  • PHP 8.2+

Using an older stack? v2.* supports PHP 7.4+, and v1.* supports even older versions. See the upgrade notes before moving to v3.

Installation

composer require fanmade/laravel-bitwise-trait

No service provider registration is needed.

How it works

Each flag is a single bit, so every flag's value is a distinct power of two (1, 2, 4, 8, …). A record's integer column is the bitwise OR of all the flags that are currently set. Because every flag owns its own bit, they never collide — you can read, set, or toggle one without touching the others.

You need one (ideally unsigned) integer column to store the flags. Pick its size based on how many flags you need — you only need one bit per flag:

$table->unsignedTinyInteger('status');   // 1 byte  -> up to 8 flags
$table->unsignedSmallInteger('status');  // 2 bytes -> up to 16 flags
$table->unsignedMediumInteger('status'); // 3 bytes -> up to 24 flags
$table->unsignedInteger('status');       // 4 bytes -> up to 32 flags

Most of the time an unsignedTinyInteger is all you need. You can add as many flag columns as you like if you outgrow a single one.

Quick start

Add the trait to your model and define one flag constant per bit. The clearest way to write the values is the shift syntax 1 << n:

<?php

namespace App\Models;

use Fanmade\Bitwise\BitwiseFlagTrait;
use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    use BitwiseFlagTrait;

    public const SENT     = 1 << 0; // 1
    public const RECEIVED = 1 << 1; // 2
    public const SEEN     = 1 << 2; // 4
    public const READ     = 1 << 3; // 8
}

The trait gives you three protected helper methods to call from inside your model. The first argument is always the name of the column that stores the flags:

  • getFlag(string $column, int $flag): bool — is the flag currently set?
  • setFlag(string $column, int $flag, bool $value): bool — set or clear the flag; returns its new state
  • toggleFlag(string $column, int $flag): bool — flip the flag; returns its new state

Because they're protected, you expose each flag through a small accessor (below) rather than calling them from outside the model.

Exposing flags as attributes

Wrap each flag in an accessor/mutator so it reads and writes like a normal boolean attribute.

Since Laravel 9 the idiomatic way is a single Attribute method per flag:

use Illuminate\Database\Eloquent\Casts\Attribute;

protected function seen(): Attribute
{
    return Attribute::make(
        get: fn (): bool => $this->getFlag('status', self::SEEN),
        set: fn (bool $value) => $this->setFlag('status', self::SEEN, $value),
    );
}

Now the flag behaves like any other attribute:

$message->seen = true;
$message->seen; // => true
$message->save();

Before Laravel 9 you'd define the accessor and mutator as separate magic methods:

public function getSeenAttribute(): bool
{
    return $this->getFlag('status', self::SEEN);
}

public function setSeenAttribute(bool $value): void
{
    $this->setFlag('status', self::SEEN, $value);
}

Querying by flag (scopes)

To filter records by a flag, add a query scope. A bit is set when column & flag = flag, and unset when column & flag = 0.

Since Laravel 12 you can use the #[Scope] attribute, which drops the old scope method-name prefix:

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;

#[Scope]
protected function read(Builder $query): void
{
    $query->whereRaw('(status & ?) = ?', [self::READ, self::READ]);
}

#[Scope]
protected function unread(Builder $query): void
{
    $query->whereRaw('(status & ?) = 0', [self::READ]);
}
Message::read()->get();
Message::unread()->get();

Before Laravel 12, name the method with a scope prefix instead of using the attribute:

public function scopeRead(Builder $query): Builder
{
    return $query->whereRaw('(status & ?) = ?', [self::READ, self::READ]);
}

public function scopeUnread(Builder $query): Builder
{
    return $query->whereRaw('(status & ?) = 0', [self::READ]);
}

Writing flag values

Beyond the shift syntax, you can define the constants however reads best to you — all three of these are equivalent:

public const SENT = 8;         // decimal
public const SENT = 1 << 3;    // bit shift
public const SENT = 0b0001000; // binary literal

Testing

composer install
vendor/bin/phpunit

Credits

License

The MIT License (MIT). See License File for more information.