Added Readme
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
# tez/utils-enum
|
||||
|
||||
Composable PHP 8.3 traits that extend native enums with lookup, comparison, navigation, bitmask, label, and collection helpers.
|
||||
|
||||
**Zero dependencies** · **PHPStan level 9** · **MIT**
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
composer require tez/utils-enum
|
||||
```
|
||||
|
||||
Requires PHP ≥ 8.3.
|
||||
|
||||
## Traits
|
||||
|
||||
| Trait | Enum type | Purpose |
|
||||
|---|---|---|
|
||||
| `EnumTrait` | Unit | Base helpers: lookup, filtering, ordering |
|
||||
| `BackedEnumTrait` | Backed | Extends `EnumTrait` with value-based helpers |
|
||||
| `BitFlagTrait` | Int-backed | Bitmask operations |
|
||||
| `ComparableTrait` | Int-backed | Comparison and range checks |
|
||||
| `StepTrait` | Any | Navigate to adjacent cases |
|
||||
| `TranslatableTrait` | Any | Human-readable labels |
|
||||
| `EnumCollectionTrait` | Any | Collection-style selection |
|
||||
|
||||
---
|
||||
|
||||
## EnumTrait
|
||||
|
||||
Base trait for pure (unit) enums. Use `BackedEnumTrait` instead when your enum has backing values.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\EnumTrait;
|
||||
|
||||
enum Status
|
||||
{
|
||||
use EnumTrait;
|
||||
|
||||
case Draft;
|
||||
case Published;
|
||||
case Archived;
|
||||
}
|
||||
|
||||
Status::find('Published'); // Status::Published
|
||||
Status::find('published', ignoreCase: true); // Status::Published
|
||||
Status::Published->is('Published'); // true
|
||||
Status::Published->is(Status::Published); // true
|
||||
Status::Published->isOneOf([Status::Draft, Status::Published]); // true
|
||||
Status::Published->isNoneOf([Status::Archived]); // true
|
||||
|
||||
Status::Published->ordinal(); // 1
|
||||
Status::first(); // Status::Draft
|
||||
Status::last(); // Status::Archived
|
||||
|
||||
Status::filter(fn($c) => $c !== Status::Archived); // [Draft, Published]
|
||||
Status::toArray(); // ['Draft' => 'Draft', 'Published' => 'Published', 'Archived' => 'Archived']
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `is($value)` | True if the case matches by identity or name. Null always returns false. |
|
||||
| `isOneOf(?array $values)` | True if the case matches any value in the list. |
|
||||
| `isNoneOf(?array $values)` | Inverse of `isOneOf()`. |
|
||||
| `find($value, $ignoreCase)` | Returns a matching case or null. Matches by identity or name. |
|
||||
| `filter(callable $cb)` | Returns all cases for which the callback returns true. |
|
||||
| `toArray()` | Returns `[name => name]` for all cases. |
|
||||
| `ordinal()` | Zero-based position of this case in the enum definition. |
|
||||
| `first()` | Returns the first defined case. |
|
||||
| `last()` | Returns the last defined case. |
|
||||
|
||||
---
|
||||
|
||||
## BackedEnumTrait
|
||||
|
||||
Extends `EnumTrait` for string- or int-backed enums. Overrides `is()` to match by backing value, and `toArray()` to return `[value => name]`.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\BackedEnumTrait;
|
||||
|
||||
enum Color: string
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
|
||||
case Red = 'red';
|
||||
case Green = 'green';
|
||||
case Blue = 'blue';
|
||||
}
|
||||
|
||||
Color::find('red'); // Color::Red — matched by value
|
||||
Color::find('Red'); // Color::Red — matched by name
|
||||
Color::find('RED', ignoreCase: true); // Color::Red
|
||||
|
||||
Color::hasValue('blue'); // true
|
||||
Color::values(); // ['red', 'green', 'blue']
|
||||
Color::fromName('Green'); // Color::Green (throws ValueError if not found)
|
||||
Color::tryFromName('X'); // null
|
||||
|
||||
Color::toArray(); // ['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue']
|
||||
Color::toValueArray(); // ['Red' => 'red', 'Green' => 'green', 'Blue' => 'blue']
|
||||
```
|
||||
|
||||
> **`toArray()` vs `toValueArray()`** — `toArray()` returns `[value => name]`, useful for form selects.
|
||||
> `toValueArray()` returns `[name => value]`, useful for serialization. The directions are opposite.
|
||||
|
||||
### Additional methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `values()` | All backing values as a list. |
|
||||
| `hasValue($value)` | True if any case has this backing value. |
|
||||
| `fromName(string $name)` | Returns a case by name, or throws `ValueError`. |
|
||||
| `tryFromName(string $name)` | Returns a case by name, or null. |
|
||||
| `toValueArray()` | Returns `[name => value]` for all cases. |
|
||||
|
||||
---
|
||||
|
||||
## BitFlagTrait
|
||||
|
||||
Bitmask helpers for int-backed enums used as flag sets. Case values must be powers of 2 (`1, 2, 4, 8, …`). This constraint is not enforced at runtime.
|
||||
|
||||
Does not include `BackedEnumTrait` — combine them explicitly if needed.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\BitFlagTrait;
|
||||
|
||||
enum Permission: int
|
||||
{
|
||||
use BitFlagTrait;
|
||||
|
||||
case Read = 1;
|
||||
case Write = 2;
|
||||
case Execute = 4;
|
||||
}
|
||||
|
||||
// Build a mask
|
||||
$mask = Permission::combine(Permission::Read, Permission::Write); // 3
|
||||
|
||||
// Check flags
|
||||
Permission::Read->isSetIn($mask); // true
|
||||
Permission::Execute->isSetIn($mask); // false
|
||||
|
||||
// Add / remove flags
|
||||
$mask = Permission::Execute->addTo($mask); // 7
|
||||
$mask = Permission::Write->removeFrom($mask); // 5
|
||||
|
||||
// Expand a mask back to cases
|
||||
Permission::fromBitmask($mask); // [Permission::Read, Permission::Execute]
|
||||
Permission::fromBitmask(0); // []
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `combine(self ...$cases)` | OR-combines case values into a bitmask. Returns 0 with no arguments. |
|
||||
| `fromBitmask(int $mask)` | Returns all cases whose bit is set in the mask. Skips cases with value 0. |
|
||||
| `isSetIn(int $mask)` | True if this case's bit is set in the mask. |
|
||||
| `addTo(int $mask)` | Returns the mask with this case's bit set. |
|
||||
| `removeFrom(int $mask)` | Returns the mask with this case's bit cleared. |
|
||||
|
||||
---
|
||||
|
||||
## ComparableTrait
|
||||
|
||||
Comparison helpers for int-backed enums, comparing cases by their backing values.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\ComparableTrait;
|
||||
|
||||
enum Priority: int
|
||||
{
|
||||
use ComparableTrait;
|
||||
|
||||
case Low = 1;
|
||||
case Medium = 2;
|
||||
case High = 3;
|
||||
}
|
||||
|
||||
Priority::High->isGreaterThan(Priority::Low); // true
|
||||
Priority::Medium->isGreaterThanOrEqual(Priority::Medium); // true
|
||||
Priority::Medium->between(Priority::Low, Priority::High); // true
|
||||
Priority::High->clamp(Priority::Low, Priority::Medium); // Priority::Medium
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `isGreaterThan(self $other)` | True if this case's value is strictly greater. |
|
||||
| `isGreaterThanOrEqual(self $other)` | True if this case's value is greater or equal. |
|
||||
| `isLessThan(self $other)` | True if this case's value is strictly less. |
|
||||
| `isLessThanOrEqual(self $other)` | True if this case's value is less or equal. |
|
||||
| `between($min, $max)` | True if within the inclusive range `[$min, $max]`. |
|
||||
| `clamp($min, $max)` | Returns this case, or `$min`/`$max` if out of range. |
|
||||
|
||||
---
|
||||
|
||||
## StepTrait
|
||||
|
||||
Navigate to adjacent cases by definition order. Designed for state machines and ordered workflows. Works with both unit and backed enums.
|
||||
|
||||
Order is determined by **definition order** (ordinal), not backing value. Returns `null` at boundaries — no wrap-around.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\StepTrait;
|
||||
|
||||
enum CheckoutStep
|
||||
{
|
||||
use StepTrait;
|
||||
|
||||
case PersonalInfo;
|
||||
case Address;
|
||||
case Payment;
|
||||
case Confirmation;
|
||||
}
|
||||
|
||||
CheckoutStep::Address->next(); // CheckoutStep::Payment
|
||||
CheckoutStep::Address->prev(); // CheckoutStep::PersonalInfo
|
||||
|
||||
CheckoutStep::Confirmation->next(); // null (last step)
|
||||
CheckoutStep::PersonalInfo->prev(); // null (first step)
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `next()` | Returns the next case by definition order, or null at the last case. |
|
||||
| `prev()` | Returns the previous case by definition order, or null at the first case. |
|
||||
|
||||
---
|
||||
|
||||
## TranslatableTrait
|
||||
|
||||
Human-readable labels for enum cases, without any framework dependency. Override `labels()` to supply custom mappings keyed by case name. Falls back to the case name when no label is defined.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\TranslatableTrait;
|
||||
|
||||
enum UserRole
|
||||
{
|
||||
use TranslatableTrait;
|
||||
|
||||
case Admin;
|
||||
case Editor;
|
||||
case Viewer;
|
||||
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
self::Admin->name => 'Administrator',
|
||||
self::Editor->name => 'Content Editor',
|
||||
// Viewer has no entry — falls back to 'Viewer'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
UserRole::Admin->label(); // 'Administrator'
|
||||
UserRole::Viewer->label(); // 'Viewer' (fallback)
|
||||
|
||||
UserRole::labelsArray();
|
||||
// ['Admin' => 'Administrator', 'Editor' => 'Content Editor', 'Viewer' => 'Viewer']
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `label()` | Human-readable label for this case. Falls back to `$case->name`. |
|
||||
| `labelsArray()` | All cases mapped to their labels as `[caseName => label]`. |
|
||||
| `labels()` *(protected)* | Override in your enum to supply custom label mappings. |
|
||||
|
||||
---
|
||||
|
||||
## EnumCollectionTrait
|
||||
|
||||
Collection-style selection helpers. Can be combined with `EnumTrait` or `BackedEnumTrait`. Works with any enum type.
|
||||
|
||||
```php
|
||||
use Tez\Utils\Enum\EnumCollectionTrait;
|
||||
|
||||
enum Suit
|
||||
{
|
||||
use EnumCollectionTrait;
|
||||
|
||||
case Hearts;
|
||||
case Diamonds;
|
||||
case Clubs;
|
||||
case Spades;
|
||||
}
|
||||
|
||||
Suit::except(Suit::Hearts, Suit::Diamonds); // [Suit::Clubs, Suit::Spades]
|
||||
Suit::only(Suit::Clubs, Suit::Spades); // [Suit::Clubs, Suit::Spades]
|
||||
Suit::random(); // a random Suit case
|
||||
```
|
||||
|
||||
Both `except()` and `only()` preserve enum definition order.
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `random()` | Returns a random case. |
|
||||
| `except(self ...$excluded)` | All cases except the given ones, in definition order. |
|
||||
| `only(self ...$included)` | Only the given cases, in definition order. |
|
||||
|
||||
---
|
||||
|
||||
## Combining traits
|
||||
|
||||
Traits are designed to be freely mixed. A few common combinations:
|
||||
|
||||
```php
|
||||
// Full-featured backed enum
|
||||
enum Status: string
|
||||
{
|
||||
use BackedEnumTrait, TranslatableTrait, EnumCollectionTrait, StepTrait;
|
||||
|
||||
case Draft = 'draft';
|
||||
case Review = 'review';
|
||||
case Published = 'published';
|
||||
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
self::Draft->name => 'Draft',
|
||||
self::Review->name => 'In Review',
|
||||
self::Published->name => 'Published',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Status::Draft->next(); // Status::Review
|
||||
Status::Review->label(); // 'In Review'
|
||||
Status::except(Status::Draft); // [Status::Review, Status::Published]
|
||||
Status::find('draft'); // Status::Draft
|
||||
```
|
||||
|
||||
```php
|
||||
// Int-backed enum with bitmask + comparison
|
||||
enum Permission: int
|
||||
{
|
||||
use BackedEnumTrait, BitFlagTrait, ComparableTrait;
|
||||
|
||||
case Read = 1;
|
||||
case Write = 2;
|
||||
case Execute = 4;
|
||||
}
|
||||
```
|
||||
|
||||
> All traits use `@phpstan-require-implements` to enforce the correct enum type at static analysis time.
|
||||
> Mixing `BitFlagTrait` or `ComparableTrait` with non-int-backed enums will surface as a PHPStan error.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user