-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Laravel integration for gksh/bitmask #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4e71aef
feat: Add Laravel integration for gksh/bitmask
karkowg ddd8550
review tweaks
karkowg cc8bf6a
fix MakeBitmaskFlagsCommand
karkowg 2ca71ec
move phpunit.xml to phpunit.xml.dist
karkowg f4a4a5c
add bitmask:inspect and README
karkowg 5d2f70b
fix: remove redundant instanceof check in InspectBitmaskCommand
karkowg eabb428
refactor: simplify CI dependency installation
karkowg 193ed58
refactor: MakeBitmaskFlagsCommand
karkowg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| tests: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| strategy: | ||
| fail-fast: true | ||
| matrix: | ||
| php: [8.2, 8.3, 8.4] | ||
| laravel: [11.*, 12.*] | ||
| exclude: | ||
| - php: 8.2 | ||
| laravel: 12.* | ||
|
|
||
| name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup PHP | ||
| uses: shivammathur/setup-php@v2 | ||
| with: | ||
| php-version: ${{ matrix.php }} | ||
| extensions: mbstring, sqlite3 | ||
| coverage: none | ||
|
|
||
| - name: Install dependencies | ||
| run: composer update --prefer-dist --no-interaction --no-progress --with="illuminate/contracts:${{ matrix.laravel }}" | ||
|
|
||
| - name: Check code style | ||
| run: vendor/bin/pint --test | ||
|
|
||
| - name: Run PHPStan | ||
| run: vendor/bin/phpstan | ||
|
|
||
| - name: Run tests | ||
| run: vendor/bin/pest --colors=always |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,13 @@ | ||
| /.phpunit.result.cache | ||
| /.phpunit.cache | ||
| /.phpstan.cache | ||
| /.php-cs-fixer.cache | ||
| /.php-cs-fixer.php | ||
| /composer.lock | ||
| /phpunit.xml | ||
| /vendor/ | ||
| node_modules/ | ||
| npm-debug.log | ||
| yarn-error.log | ||
|
|
||
| # Laravel 4 specific | ||
| bootstrap/compiled.php | ||
| app/storage/ | ||
|
|
||
| # Laravel 5 & Lumen specific | ||
| public/storage | ||
| public/hot | ||
|
|
||
| # Laravel 5 & Lumen specific with changed public path | ||
| public_html/storage | ||
| public_html/hot | ||
|
|
||
| storage/*.key | ||
| .env | ||
| Homestead.yaml | ||
| Homestead.json | ||
| /.vagrant | ||
| .phpunit.result.cache | ||
|
|
||
| /public/build | ||
| /storage/pail | ||
| .env.backup | ||
| .env.production | ||
| .phpactor.json | ||
| auth.json | ||
| *.swp | ||
| *.swo | ||
| /.phpactor.json | ||
| /.idea | ||
| /.claude/settings.local.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| <p align="center"> | ||
| <img src="https://banners.beyondco.de/gksh%2Ftransistor.png?theme=light&packageManager=composer+require&packageName=gksh%2Ftransistor&pattern=circuitBoard&style=style_1&description=Bitwise+affordances+in+Laravel&md=1&showWatermark=0&fontSize=100px&images=lightning-bolt&widths=100&heights=100" alt="Transistor banner"> | ||
| </p> | ||
|
|
||
| # Transistor | ||
|
|
||
| Laravel integration for [gksh/bitmask](https://github.com/gksh/bitmask) - providing Eloquent casting, query scopes, validation, Blade directives, and migration macros for working with bitmask flags. | ||
|
|
||
| [](https://packagist.org/packages/gksh/transistor) | ||
| [](https://packagist.org/packages/gksh/transistor) | ||
| [](https://packagist.org/packages/gksh/transistor) | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| composer require gksh/transistor | ||
| ``` | ||
|
|
||
| The service provider is auto-discovered by Laravel. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```php | ||
| // 1. Generate a bitmask enum | ||
| php artisan make:bitmask-flags Permission | ||
|
|
||
| // 2. Add migration macro | ||
| Schema::create('users', function (Blueprint $table) { | ||
| $table->id(); | ||
| $table->bitmask('permissions'); | ||
| }); | ||
|
|
||
| // 3. Configure your model | ||
| use Gksh\Transistor\Casts\BitmaskCast; | ||
| use Gksh\Transistor\Concerns\HasBitmaskScopes; | ||
|
|
||
| class User extends Model | ||
| { | ||
| use HasBitmaskScopes; | ||
|
|
||
| protected function casts(): array | ||
| { | ||
| return [ | ||
| 'permissions' => BitmaskCast::class, | ||
| ]; | ||
| } | ||
| } | ||
|
|
||
| // 4. Use it | ||
| $user->permissions = $user->permissions | ||
| ->set(Permission::Read) | ||
| ->set(Permission::Write); | ||
|
|
||
| User::whereHasBitmaskFlag('permissions', Permission::Admin)->get(); | ||
| ``` | ||
|
|
||
| ## Artisan Commands | ||
|
|
||
| ### make:bitmask-flags | ||
|
|
||
| Generate a bitmask flags enum interactively: | ||
|
|
||
| ```bash | ||
| php artisan make:bitmask-flags | ||
| php artisan make:bitmask-flags Permission | ||
| ``` | ||
|
|
||
| Creates an enum in `app/Enums` with bit-shifted values: | ||
|
|
||
| ```php | ||
| namespace App\Enums; | ||
|
|
||
| enum Permission: int | ||
| { | ||
| case Read = 1 << 0; // 1 | ||
| case Write = 1 << 1; // 2 | ||
| case Delete = 1 << 2; // 4 | ||
| case Admin = 1 << 3; // 8 | ||
| } | ||
| ``` | ||
|
|
||
| ### bitmask:inspect | ||
|
|
||
| Display bitmask flags as a lookup table: | ||
|
|
||
| ```bash | ||
| php artisan bitmask:inspect "App\Enums\Permission" | ||
| php artisan bitmask:inspect Permission 13 | ||
| ``` | ||
|
|
||
| ``` | ||
| +--------+---------+-----------------+ | ||
| | Case | Decimal | Binary | | ||
| +--------+---------+-----------------+ | ||
| | Read | 1 | 0 0 0 0 0 0 0 1 | | ||
| | Write | 2 | 0 0 0 0 0 0 1 0 | | ||
| | Delete | 4 | 0 0 0 0 0 1 0 0 | | ||
| | Admin | 8 | 0 0 0 0 1 0 0 0 | | ||
| +--------+---------+-----------------+ | ||
| | Value | 13 | 0 0 0 0 1 1 0 1 | | ||
| +--------+---------+-----------------+ | ||
| ``` | ||
|
|
||
| When a value is provided, active bits are highlighted in green. Heavily inspired by https://laracasts.com/series/lukes-larabits/episodes/18. | ||
|
|
||
| ## Eloquent Cast | ||
|
|
||
| Cast database integers to `Bitmask` objects: | ||
|
|
||
| ```php | ||
| use Gksh\Transistor\Casts\BitmaskCast; | ||
|
|
||
| protected function casts(): array | ||
| { | ||
| return [ | ||
| 'permissions' => BitmaskCast::class, // 32-bit (default) | ||
| 'settings' => BitmaskCast::class.':tiny', // 8-bit (0-255) | ||
| 'features' => BitmaskCast::class.':small', // 16-bit (0-65,535) | ||
| 'options' => BitmaskCast::class.':medium', // 24-bit (0-16,777,215) | ||
| ]; | ||
| } | ||
| ``` | ||
|
|
||
| The cast returns a `Gksh\Bitmask\Bitmask` object with methods like `set()`, `unset()`, `toggle()`, `has()`, and `value()`. | ||
|
|
||
| ## Query Scopes | ||
|
|
||
| Add the `HasBitmaskScopes` trait to your model: | ||
|
|
||
| ```php | ||
| use Gksh\Transistor\Concerns\HasBitmaskScopes; | ||
|
|
||
| class User extends Model | ||
| { | ||
| use HasBitmaskScopes; | ||
| } | ||
| ``` | ||
|
|
||
| ### Available Scopes | ||
|
|
||
| ```php | ||
| // Filter where flag IS set | ||
| User::whereHasBitmaskFlag('permissions', Permission::Read)->get(); | ||
|
|
||
| // Filter where ALL flags are set | ||
| User::whereHasAllBitmaskFlags('permissions', [Permission::Read, Permission::Write])->get(); | ||
|
|
||
| // Filter where ANY flag is set | ||
| User::whereHasAnyBitmaskFlag('permissions', [Permission::Write, Permission::Admin])->get(); | ||
|
|
||
| // Filter where flag is NOT set | ||
| User::whereDoesntHaveBitmaskFlag('permissions', Permission::Admin)->get(); | ||
|
|
||
| // Filter where NONE of the flags are set | ||
| User::whereDoesntHaveAnyBitmaskFlag('permissions', [Permission::Read, Permission::Write])->get(); | ||
| ``` | ||
|
|
||
| Scopes can be chained: | ||
|
|
||
| ```php | ||
| User::whereHasBitmaskFlag('permissions', Permission::Read) | ||
| ->whereDoesntHaveBitmaskFlag('permissions', Permission::Admin) | ||
| ->get(); | ||
| ``` | ||
|
|
||
| ## Migration Macros | ||
|
|
||
| Create bitmask columns with the appropriate integer size: | ||
|
|
||
| ```php | ||
| Schema::create('users', function (Blueprint $table) { | ||
| $table->id(); | ||
| $table->bitmask('permissions'); // unsigned integer (32-bit) | ||
| $table->tinyBitmask('settings'); // unsigned tinyInteger (8-bit) | ||
| $table->smallBitmask('features'); // unsigned smallInteger (16-bit) | ||
| $table->mediumBitmask('options'); // unsigned mediumInteger (24-bit) | ||
| }); | ||
| ``` | ||
|
|
||
| All macros set a default value of `0`. | ||
|
|
||
| ## Validation | ||
|
|
||
| ### As a Rule Object | ||
|
|
||
| ```php | ||
| use Gksh\Transistor\Rules\ValidBitmask; | ||
|
|
||
| $validated = $request->validate([ | ||
| 'permissions' => ['required', new ValidBitmask()], | ||
| 'role_flags' => ['required', new ValidBitmask(Permission::class)], | ||
| ]); | ||
| ``` | ||
|
|
||
| ### As a String Rule | ||
|
|
||
| ```php | ||
| $validated = $request->validate([ | ||
| 'permissions' => 'required|bitmask', | ||
| 'role_flags' => 'required|bitmask:App\Enums\Permission', | ||
| ]); | ||
| ``` | ||
|
|
||
| When an enum class is provided, the rule ensures the value doesn't exceed the maximum possible combination of all flags. | ||
|
|
||
| ## Blade Directives | ||
|
|
||
| ```blade | ||
| @hasBitmaskFlag($user->permissions, Permission::Admin) | ||
| <span>Administrator</span> | ||
| @endif | ||
|
|
||
| @hasAnyBitmaskFlag($user->permissions, [Permission::Write, Permission::Admin]) | ||
| <button>Edit</button> | ||
| @endif | ||
|
|
||
| @hasAllBitmaskFlags($user->permissions, [Permission::Read, Permission::Write]) | ||
| <span>Full Access</span> | ||
| @endif | ||
| ``` | ||
|
|
||
| ## Requirements | ||
|
|
||
| - PHP 8.2+ | ||
| - Laravel 11.x or 12.x | ||
|
|
||
| ## License | ||
|
|
||
| MIT |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| { | ||
| "name": "gksh/transistor", | ||
| "description": "Laravel integration for gksh/bitmask", | ||
| "keywords": ["laravel", "bitmask", "bitwise", "flags", "eloquent", "cast"], | ||
| "license": "MIT", | ||
| "authors": [ | ||
| { | ||
| "name": "Gustavo Karkow", | ||
| "email": "karkowg@gmail.com" | ||
| } | ||
| ], | ||
| "require": { | ||
| "php": "^8.2", | ||
| "gksh/bitmask": "^1.0", | ||
| "illuminate/contracts": "^11.0|^12.0", | ||
| "illuminate/database": "^11.0|^12.0", | ||
| "illuminate/support": "^11.0|^12.0", | ||
| "laravel/prompts": "^0.1|^0.2|^0.3" | ||
| }, | ||
| "require-dev": { | ||
| "laravel/pint": "^1.18", | ||
| "orchestra/testbench": "^9.0|^10.0", | ||
| "pestphp/pest": "^3.0|^4.0", | ||
| "phpstan/phpstan": "^2.0" | ||
| }, | ||
| "autoload": { | ||
| "psr-4": { | ||
| "Gksh\\Transistor\\": "src/" | ||
| } | ||
| }, | ||
| "autoload-dev": { | ||
| "psr-4": { | ||
| "Gksh\\Transistor\\Tests\\": "tests/" | ||
| } | ||
| }, | ||
| "extra": { | ||
| "laravel": { | ||
| "providers": [ | ||
| "Gksh\\Transistor\\TransistorServiceProvider" | ||
| ] | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "lint": "pint --test", | ||
| "lint:fix": "pint", | ||
| "test": "pest", | ||
| "analyse": "phpstan", | ||
| "ci": [ | ||
| "@lint", | ||
| "@analyse", | ||
| "@test" | ||
| ] | ||
| }, | ||
| "config": { | ||
| "sort-packages": true, | ||
| "allow-plugins": { | ||
| "pestphp/pest-plugin": true | ||
| } | ||
| }, | ||
| "minimum-stability": "stable", | ||
| "prefer-stable": true | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| includes: | ||
| - ./vendor/phpstan/phpstan/conf/bleedingEdge.neon | ||
|
|
||
| parameters: | ||
| level: max | ||
| paths: | ||
| - src | ||
| tmpDir: .phpstan.cache | ||
| ignoreErrors: | ||
| - '#Trait .+ is used zero times and is not analysed#' |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.