Skip to content

Latest commit

 

History

History
106 lines (82 loc) · 4.3 KB

Bitmasked.md

File metadata and controls

106 lines (82 loc) · 4.3 KB

Bitmasked Behavior

A CakePHP behavior to allow quick row-level filtering of models via bitmasks.

Introduction

Basically it encodes the array of bit flags into a single bitmask on save and vice versa on find. I created it as an extension of my pretty well working Enum stuff. It can use this type of enum declaration for our bitmask, as well. It uses constants as this is the cleanest approach to define model based field values that need to be hardcoded in your application.

Technical limitation

The theoretical limit for a 64-bit integer [SQL: BIGINT unsigned] would be 64 bits (2^64). Don’t use bitmasks if you seem to need more than a hand full, though. Then you obviously do something wrong and should better use a join table. I highly recommend using tinyint(3) unsigned which can hold up to 8 bits – more than enough. It still only needs 1 byte.

Usage

Attach it to your model's Table class in its initialize() method like so:

$this->addBehavior('Tools.Bitmasked', $options);

If you want to alias the field for output:

$this->addBehavior('Tools.Bitmasked', ['mappedField' => 'statuses', 'field' => 'status']);

The mappedField param is quite handy if you want more control over your bitmask. It stores the array under this alias and does not override the bitmask key. So in our case status will always contain the integer bitmask and statuses the verbose array of it.

Defining the selectable values

We first define values and make sure they follow the bitmask scheme:

1, 2, 4, 8, 16, 32, 64, 128, ...

I recommend using a DRY enum approach, using your entity:

// A bunch of bool values
const STATUS_ACTIVE = 1;
const STATUS_FEATURED = 2;
const STATUS_APPROVED = 4;
const STATUS_FLAGGED = 8;

...

public static function statuses($value = null) {
	$options = [
		self::STATUS_ACTIVE => __('Active'),
		self::STATUS_FEATURED => __('Featured'),
		self::STATUS_APPROVED => __('Approved'),
		self::STATUS_FLAGGED => __('Flagged'),
	];	
	return parent::enum($value, $options);
}

Please note that you need to define Entity::enum() by extending my Tools Entity base class or by putting it into your own base class manually. You don’t have to use the enum approach, though.

Of course it only makes sense to use bitmasks, if those values can co-exist, if you can select multiple at once. Otherwise you would want to store them separately anyway. Obviously you could also just use four or more boolean fields to achieve the same thing.

So now, in the add/edit form we can:

echo $this->Form->control('statuses', ['options' => Comment::statuses(), 'multiple' => 'checkbox']);

Tip: Usually, you have passed down the current entity for the form building anyway, then you don't need static access:

echo $this->Form->create($comment);
echo $this->Form->control('statuses', ['options' => $comment->statuses(), 'multiple' => 'checkbox']);
...

It will save the final bitmask to the database field status as integer. For example "active and approved" would become 9.

Custom finder

You can use the built in custom finder findBitmasked:

$statuses = [Comment::STATUS_ACTIVE, Comment::STATUS_FEATURED];
$comments = $this->Comments->find('bits', ['bits' => $statuses])->toArray();

Using Search plugin

If you use Search plugin, you can easily make a filter as multi-checkbox (or any multi-select type):

echo $this->Form->control('status', ['options' => Comment::statuses(), 'multiple' => 'checkbox', 'empty' => ' - no filter - ']);

And in your Table searchManager() setup:

$searchManager
	// We need to map the posted "status" key to the finder required "bits" key
	->finder('status', ['finder' => 'bits', 'map' => ['bits' => 'status']])

This way the array of checkboxes selected will be turned into the integer bitmask needed for the query to work.

Note: This requires Search ^4.2.1!

Outview

You can read more about how it began in my blog post.

If you want to use a more DB or Config driven approach towards enums, you can also look into other plugins and CakePHP resources available, e.g. this implementation.