Skip to content

Commit

Permalink
EnumBitMask for ^8.1 (#26)
Browse files Browse the repository at this point in the history
EnumBitMask for ^8.1
  • Loading branch information
yaroslavche authored Oct 25, 2022
1 parent 0cad165 commit 504a0c0
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 9 deletions.
23 changes: 17 additions & 6 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,34 @@ jobs:
strategy:
matrix:
operating-system: [ubuntu-latest]
php-versions: ['7.4', '8.0', '8.1']
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
php-version: ['7.4', '8.0', '8.1', '8.2']
name: PHP ${{ matrix.php-version }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
php-version: ${{ matrix.php-version }}
coverage: xdebug
tools: composer
- name: Install package
run: |
composer install
- name: Checks
- name: Phpstan rules for PHP ${{ matrix.php-version }}
if: ${{ matrix.php-version == '7.4' || matrix.php-version == '8.0' }}
run: cp phpstan_below_81.neon phpstan.neon
- name: Main checks
run: |
composer ci:pack
composer phpcs
composer phpstan
composer phpunit
- name: Infection and coverage
if: ${{ matrix.php-version == '8.1' || matrix.php-version == '8.2' }}
run: |
composer infection
composer coverage
composer phpunit
- name: Upload codecoverage
run: |
bash <(curl -s https://codecov.io/bash)
bash <(curl -s https://codecov.io/bash)
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[![PHP build](https://github.com/yaroslavche/BitMask/actions/workflows/php.yml/badge.svg)](https://github.com/yaroslavche/BitMask/actions/workflows/php.yml)
[![codecov](https://codecov.io/gh/yaroslavche/bitmask/branch/main/graph/badge.svg)](https://codecov.io/gh/yaroslavche/bitmask)
[![Infection MSI](https://badge.stryker-mutator.io/github.com/yaroslavche/BitMask/main)](https://infection.github.io)
[![PHP](http://poser.pugx.org/yaroslavche/bitmask/require/php)](https://packagist.org/packages/yaroslavche/bitmask)
# BitMask

PHP library for working with bitmask values
Expand Down Expand Up @@ -87,6 +88,33 @@ $bitmask->executable = false;
$bitmask->unknownKey = true; // BitMask\Exception\UnknownKeyException
```

With PHP ^8.1 `EnumBitMask` can be used:

```php
enum Permissions {
case Read;
case Write;
case Execute;
}

use BitMask\EnumBitMask;

// First argument is required and expects FQCN of the enum
// Second argument is a variadic UnitEnum, and acts like setter of the bits
$bitmask = new EnumBitMask(Permissions::class, Permissions::Read, Permissions::Execute);
// previous statement is equals to following lines:
// $bitmask = new EnumBitMask(Permissions::class);
// $bitmask->set(Permissions::Read, Permissions::Execute); // set, isSet and unset also have variadic args

$bitmask->isSet(Permissions::Read); // true
$bitmask->isSet(Permissions::Write); // false
$bitmask->isSet(Permissions::Execute); // true
$bitmask->set(Permissions::Write);
$bitmask->isSet(Permissions::Write, Permissions::Read); // true
$bitmask->unset(Permissions::Write);
$bitmask->isSet(Permissions::Write); // false
```

## Installing

Install package via [composer](https://getcomposer.org/)
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"php8"
],
"require": {
"php": "^7.4|^8.0|^8.1"
"php": "^7.4|^8.0"
},
"require-dev": {
"phpunit/phpunit": "*",
Expand Down
4 changes: 2 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
includes:
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
parameters:
level: 9
6 changes: 6 additions & 0 deletions phpstan_below_81.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parameters:
level: 9
excludePaths:
analyseAndScan:
- src/EnumBitMask.php
- tests/EnumBitMaskTest.php
79 changes: 79 additions & 0 deletions src/EnumBitMask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php // phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols

declare(strict_types=1);

namespace BitMask;

use BitMask\Exception\UnknownEnumException;
use BitMask\Exception\UnsupportedPhpVersionException;
use UnitEnum;

use const PHP_VERSION_ID;

PHP_VERSION_ID >= 80100 || throw new UnsupportedPhpVersionException('Requires PHP 8.1 interface UnitEnum');
class EnumBitMask
{
private int $bitmask = 0;
/** @var UnitEnum[] $keys */
private array $keys = [];

/**
* @param class-string $maskEnum
* @throws UnknownEnumException
*/
public function __construct(
private readonly string $maskEnum,
UnitEnum ...$bits,
) {
if (!is_subclass_of($this->maskEnum, UnitEnum::class)) {
throw new UnknownEnumException('BitMask enum must be instance of UnitEnum');
}
$this->keys = $this->maskEnum::cases();
$this->set(...$bits);
}

public function get(): int
{
return $this->bitmask;
}

/** @throws UnknownEnumException */
public function set(UnitEnum ...$bits): void
{
foreach ($bits as $bit) {
if (!$this->isSet($bit)) {
$this->bitmask += 1 << intval(array_search($bit, $this->keys));
}
}
}

/** @throws UnknownEnumException */
public function unset(UnitEnum ...$bits): void
{
foreach ($bits as $bit) {
if ($this->isSet($bit)) {
$this->bitmask -= 1 << intval(array_search($bit, $this->keys));
}
}
}

/** @throws UnknownEnumException */
public function isSet(UnitEnum ...$bits): bool
{
foreach ($bits as $bit) {
$this->checkEnumCase($bit);
$mask = 1 << intval(array_search($bit, $this->keys));
if (($this->bitmask & $mask) !== $mask) {
return false;
}
}
return true;
}

/** @throws UnknownEnumException */
private function checkEnumCase(UnitEnum $case): void
{
$case instanceof $this->maskEnum ||
throw new UnknownEnumException(sprintf('Expected %s enum case, %s provided', $this->maskEnum, $case::class));
}
}
11 changes: 11 additions & 0 deletions src/Exception/UnknownEnumException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace BitMask\Exception;

use Exception;

final class UnknownEnumException extends Exception
{
}
11 changes: 11 additions & 0 deletions src/Exception/UnsupportedPhpVersionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace BitMask\Exception;

use Exception;

final class UnsupportedPhpVersionException extends Exception
{
}
73 changes: 73 additions & 0 deletions tests/EnumBitMaskTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace BitMask\Tests;

use BitMask\EnumBitMask;
use BitMask\Exception\UnknownEnumException;
use BitMask\Tests\fixtures\Enum\Permissions;
use BitMask\Tests\fixtures\Enum\Unknown;
use PHPUnit\Framework\TestCase;
use function PHPUnit\Framework\assertFalse;
use function PHPUnit\Framework\assertSame;
use function PHPUnit\Framework\assertTrue;
use const PHP_VERSION_ID;

final class EnumBitMaskTest extends TestCase
{
protected function setUp(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('PHP ^8.1 only');
}
}

public function testNotAnEnum(): void
{
$this->expectException(UnknownEnumException::class);
new EnumBitMask(self::class);
}

public function testUnknownEnum(): void
{
$this->expectException(UnknownEnumException::class);
new EnumBitMask(Permissions::class, Unknown::Case);
}

public function testGet(): void
{
$enumBitmask = new EnumBitMask(Permissions::class, Permissions::Create, Permissions::Read);
assertSame(3, $enumBitmask->get());
$this->expectException(UnknownEnumException::class);
$enumBitmask->isSet(Unknown::Case);
}

public function testIsSet(): void
{
$enumBitmask = new EnumBitMask(Permissions::class, Permissions::Create, Permissions::Read);
assertTrue($enumBitmask->isSet(Permissions::Create));
assertTrue($enumBitmask->isSet(Permissions::Read));
assertFalse($enumBitmask->isSet(Permissions::Update));
assertFalse($enumBitmask->isSet(Permissions::Delete));
$this->expectException(UnknownEnumException::class);
$enumBitmask->isSet(Unknown::Case);
}

public function testSetUnset(): void
{
$enumBitmask = new EnumBitMask(Permissions::class, Permissions::Create, Permissions::Read);
$enumBitmask->unset(Permissions::Create, Permissions::Read);
assertFalse($enumBitmask->isSet(Permissions::Create));
assertFalse($enumBitmask->isSet(Permissions::Read));
assertFalse($enumBitmask->isSet(Permissions::Read, Permissions::Update));
assertSame(0, $enumBitmask->get());
$enumBitmask->set(Permissions::Update, Permissions::Delete);
assertTrue($enumBitmask->isSet(Permissions::Update));
assertTrue($enumBitmask->isSet(Permissions::Delete));
assertTrue($enumBitmask->isSet(Permissions::Update, Permissions::Delete));
assertSame(12, $enumBitmask->get());
$this->expectException(UnknownEnumException::class);
$enumBitmask->unset(Unknown::Case);
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/Enum/Permissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace BitMask\Tests\fixtures\Enum;

enum Permissions
{
case Create;
case Read;
case Update;
case Delete;
}
8 changes: 8 additions & 0 deletions tests/fixtures/Enum/Unknown.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace BitMask\Tests\fixtures\Enum;

enum Unknown
{
case Case;
}

0 comments on commit 504a0c0

Please sign in to comment.