ph-il / enums
Zero-dependencies package to supercharge enum functionalities via helper traits.
Requires
- php: ^8.3
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.9
- phpstan/extension-installer: ^1.4
- phpstan/phpdoc-parser: ^2.3
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.5
- rector/rector: ^2.3
- symplify/phpstan-rules: ^14.9
README
A zero-dependencies collection of enum helper traits for PHP.
Tip
Need to supercharge enums in a Symfony application?
Consider using Enums Bundle instead.
Available Traits
AsInvocableEnum— Access enum values via static calls or invocationAsNameableEnum— Get a list of case namesAsValuableEnum— Get a list of case valuesAsSelectableEnum— Get an associative array of names → valuesAsComparableEnum— Compare enum cases fluentlyAsFromambleEnum— Addfrom()/tryFrom()to pure enums andfromName()/tryFromName()to all enumsAsMetadableEnum— Attach metadata to enum cases via attributesAsSelfAwareableEnum— Introspect whether an enum is pure, backed, backed by int or stringAsStringSelectableEnum— Generate string representations of enum options
Table of Contents
- Installation
- Usage
AsInvocableEnumAsNameableEnumAsValuableEnumAsSelectableEnumAsComparableEnum- Apply the trait on your enum
is()— Check if the case matches a target- ``isNot()
— Check if the case does not match a target in()— Check if the case is in a list of targetsnotIn()— Check if the case is not in a list of targetshas()— Check if the enum includes a given target (static)doesntHave()— Check if the enum does not include a given target (static)equalsOneOf()— Check if the case matches any case in an arraynotEqualsOneOf()— Check if the case does not match any case in an array
AsFromambleEnumAsMetadableEnumAsSelfAwareableEnumAsStringSelectableEnum
- PHPStan
- Development
- Todo
Installation
PHP 8.3+ is required.
Via Composer :
composer require ph-il/enums
Usage
To supercharge our enums with all the features (except AsStringSelectableEnum) provided by this package, we can let our enums use the Enumerates trait:
use Phil\Enums\Traits\AsEnumerableEnumTrait; enum PureEnum { use AsEnumerableEnumTrait; case ONE; case TWO; case THREE; } enum BackedEnum: int { use AsEnumerableEnumTrait; case ONE = 1; case TWO = 2; case THREE = 3; }
AsInvocableEnum
Get the value of a backed enum, or the name of a pure enum, by "invoking" it — either statically (BackedEnum::THREE() instead of BackedEnum::THREE->value), or as an instance ($enum()).
Use enums as array keys without appending ->value:
'statuses' => [ TaskStatus::INCOMPLETE() => ['some configuration'], TaskStatus::COMPLETED() => ['some configuration'], ],
Or pass primitive values directly:
public function updateStatus(int $status): void; $task->updateStatus(TaskStatus::COMPLETED());
The main point: this is all without having to append ->value to everything.
This approach also has decent IDE support. You get autosuggestions while typing, and then you just append ():
BackedEnum::THREE; // => BackedEnum instance BackedEnum::THREE(); // => 3
Apply the trait on your enum
use Phil\Enums\Traits\AsInvocableEnumTrait; enum TaskStatus: int { use AsInvocableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsInvocableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; } enum PureEnum { use AsInvocableEnumTrait; case ONE; case TWO; case THREE; } enum BackedEnum: int { use AsInvocableEnumTrait; case ONE = 1; case TWO = 2; case THREE = 3; }
Use static calls to get the primitive value
BackedEnum::ONE(); // 1 BackedEnum::TWO(); // 2 BackedEnum::THREE(); // 3 PureEnum::ONE(); // 'ONE' PureEnum::TWO(); // 'TWO' PureEnum::THREE(); // 'THREE'
Invoke instances to get the primitive value
public function updateStatus(TaskStatus $status, Role $role) { $this->record->setStatus($status(), $role()); }
AsNameableEnum
Returns a list of case names in the enum.
Apply the trait on your enum
use Phil\Enums\Traits\AsNameableEnumTrait; enum TaskStatus: int { use AsNameableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsNameableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
Use the names() method
TaskStatus::names(); // ['INCOMPLETE', 'COMPLETED', 'CANCELED'] Role::names(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
AsValuableEnum
Returns a list of case values for backed enums, or a list of case names for pure enums.
Apply the trait on your enum
use Phil\Enums\Traits\AsValuableEnumTrait; enum TaskStatus: int { use AsValuableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsValuableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
Use the values() method
TaskStatus::values(); // [0, 1, 2] Role::values(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
AsSelectableEnum
Returns an associative array of [case name => case value] for backed enums, or an indexed array of names for pure enums.
Apply the trait on your enum
use Phil\Enums\Traits\AsSelectableEnumTrait; enum TaskStatus: int { use AsSelectableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsSelectableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
Use the options() method
TaskStatus::options(); // ['INCOMPLETE' => 0, 'COMPLETED' => 1, 'CANCELED' => 2] Role::options(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
AsComparableEnum
Compare enum cases using fluent instance methods and static helpers.
Apply the trait on your enum
use Phil\Enums\Traits\AsComparableEnumTrait; enum TaskStatus: int { use AsComparableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsComparableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
is() — Check if the case matches a target
TaskStatus::INCOMPLETE->is(TaskStatus::INCOMPLETE); // true TaskStatus::INCOMPLETE->is(TaskStatus::COMPLETED); // false Role::ADMINISTRATOR->is(Role::ADMINISTRATOR); // true Role::ADMINISTRATOR->is(Role::NOBODY); // false
isNot() — Check if the case does not match a target
TaskStatus::INCOMPLETE->isNot(TaskStatus::INCOMPLETE); // false TaskStatus::INCOMPLETE->isNot(TaskStatus::COMPLETED); // true Role::ADMINISTRATOR->isNot(Role::ADMINISTRATOR); // false Role::ADMINISTRATOR->isNot(Role::NOBODY); // true
in() — Check if the case is in a list of targets
TaskStatus::INCOMPLETE->in([TaskStatus::INCOMPLETE, TaskStatus::COMPLETED]); // true TaskStatus::INCOMPLETE->in([TaskStatus::COMPLETED, TaskStatus::CANCELED]); // false Role::ADMINISTRATOR->in([Role::ADMINISTRATOR, Role::GUEST]); // true Role::ADMINISTRATOR->in([Role::SUBSCRIBER, Role::GUEST]); // false
notIn() — Check if the case is not in a list of targets
TaskStatus::INCOMPLETE->notIn([TaskStatus::INCOMPLETE, TaskStatus::COMPLETED]); // false TaskStatus::INCOMPLETE->notIn([TaskStatus::COMPLETED, TaskStatus::CANCELED]); // true Role::ADMINISTRATOR->notIn([Role::ADMINISTRATOR, Role::GUEST]); // false Role::ADMINISTRATOR->notIn([Role::SUBSCRIBER, Role::GUEST]); // true
has() — Check if the enum includes a given target (static)
TaskStatus::has(TaskStatus::INCOMPLETE); // true
doesntHave() — Check if the enum does not include a given target (static)
TaskStatus::doesntHave(TaskStatus::INCOMPLETE); // false
equalsOneOf() — Check if the case matches any case in an array
TaskStatus::INCOMPLETE->equalsOneOf([TaskStatus::INCOMPLETE, TaskStatus::COMPLETED]); // true
notEqualsOneOf() — Check if the case does not match any case in an array
TaskStatus::INCOMPLETE->notEqualsOneOf([TaskStatus::COMPLETED, TaskStatus::CANCELED]); // true
AsFromambleEnum
Adds from() and tryFrom() to pure enums, and adds fromName() and tryFromName() to all enums.
Important Notes:
BackedEnuminstances already implement their ownfrom()andtryFrom()methods, which will not be overridden by this trait.- For pure enums,
from()andtryFrom()are functionally equivalent tofromName()andtryFromName().
Apply the trait on your enum
use Phil\Enums\AsFromambleEnumTrait; enum TaskStatus: int { use AsFromambleEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsFromambleEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
from() — Get a pure enum case by name or throw
Role::from('ADMINISTRATOR'); // Role::ADMINISTRATOR Role::from('NOBODY'); // Error: ValueError
tryFrom() — Get a pure enum case by name or null
Role::tryFrom('GUEST'); // Role::GUEST Role::tryFrom('NEVER'); // null
fromName() — Get any enum case by name or throw
TaskStatus::fromName('INCOMPLETE'); // TaskStatus::INCOMPLETE TaskStatus::fromName('MISSING'); // Error: ValueError Role::fromName('SUBSCRIBER'); // Role::SUBSCRIBER Role::fromName('HACKER'); // Error: ValueError
tryFromName() — Get any enum case by name or null
TaskStatus::tryFromName('COMPLETED'); // TaskStatus::COMPLETED TaskStatus::tryFromName('NOTHING'); // null Role::tryFromName('GUEST'); // Role::GUEST Role::tryFromName('TESTER'); // null
AsMetadableEnum
Attach metadata to enum cases using PHP attributes.
Apply the trait on your enum
use App\Enums\MetaProperties\Color; use Phil\Enums\AsMetadableEnumTrait; use Phil\Enums\Attribute\Description; use Phil\Enums\Attribute\Meta; #[Meta(Description::class, Color::class)] enum TaskStatus: int { use AsMetadableEnumTrait; #[Description('Incomplete Task')] #[Color('red')] case INCOMPLETE = 0; #[Description('Completed Task')] #[Color('green')] case COMPLETED = 1; #[Description('Canceled Task')] #[Color('gray')] case CANCELED = 2; }
Explanation:
Descriptionis an exemple that we do provide as a class attributes for meta propertiesColoris userland class attributes — meta properties- The
#[Meta]attribute on the enum declares which meta properties are enabled - Each case must have the declared meta properties applied (unless the meta property defines a
defaultValue())
Access the metadata
TaskStatus::INCOMPLETE->description(); // 'Incomplete Task' TaskStatus::COMPLETED->color(); // 'green'
Creating meta properties
Each meta property is a class extending AbstractMetaProperty:
#[Attribute] class Color extends AbstractMetaProperty {} #[Attribute] class Description extends AbstractMetaProperty {}
Inside the class, you can customize a few things.
Custom method name — Override customMethodName() to change the accessor
For instance, you may want to use a different method name than the one derived from the class name (Description becomes description() by default).
To do that, override the method() method on the meta property:
#[Attribute] class Description extends AbstractMetaProperty { public static function customMethodName(): ?string { return 'note'; } }
With the code above, the description of a case will be accessible as TaskStatus::INCOMPLETE->note().
Value transformation — Override transform() to modify the stored value
Another thing you can customize is the passed value. For instance, to wrap a color name like text-{$color}-500, you'd add the following transform() method:
#[Attribute] class Color extends MetaProperty { protected function transform(mixed $value): mixed { return "text-{$value}-500"; } }
And now the returned color will be correctly transformed:
TaskStatus::COMPLETED->color(); // 'text-green-500'
Default value — Override defaultValue() so cases without the attribute still return a value
You can also add a defaultValue() method to specify the value a case should have if it doesn't use the meta property. That way you can apply the attribute only on some cases and still get a configurable default value on all other cases.
fromMeta() — Get the first case matching a meta value or throw
TaskStatus::fromMeta(Color::make('green')); // TaskStatus::COMPLETED TaskStatus::fromMeta(Color::make('blue')); // Error: ValueError
tryFromMeta() — Get the first case matching a meta value or null
TaskStatus::tryFromMeta(Color::make('green')); // TaskStatus::COMPLETED TaskStatus::tryFromMeta(Color::make('blue')); // null
Included Meta Properties
The package ships with three ready-to-use meta properties:
Description
This attribute is a Single Value
Group
This attribute is Repeatable
Label
This attribute is a Single Value
Recommendation: use annotations and traits
For better IDE support, add @method annotations:
/** * @method string description() * @method string color() */ #[Meta(Description::class, Color::class)] enum TaskStatus: int { use Metadata; #[Description('Incomplete Task')] #[Color('red')] case INCOMPLETE = 0; #[Description('Completed Task')] #[Color('green')] case COMPLETED = 1; #[Description('Canceled Task')] #[Color('gray')] case CANCELED = 2; }
If you reuse the same meta property across multiple enums, create a dedicated trait with the @method annotation.
AsSelfAwareableEnum
Introspect the nature of your enum at runtime.
Apply the trait on your enum
use Phil\Enums\AsSelfAwareableEnumTrait; enum TaskStatus: int { use AsSelfAwareableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsSelfAwareableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
isPure() — Check if the enum is a pure enum (no backing type)
Role::isPure(); // true TaskStatus::isPure(); // false
isBacked() — Check if the enum is backed
TaskStatus::isBacked(); // true Role::isBacked(); // false
isBackedByInteger() — Check if the enum is backed by int
TaskStatus::isBackedByInteger(); // true
isBackedByString() — Check if the enum is backed by string
TaskStatus::isBackedByString(); // false
AsStringSelectableEnum
Generate string representations of your enum options. This trait also includes AsSelectableEnumTrait.
Apply the trait on your enum
use Phil\Enums\AsStringSelectableEnumTrait; enum TaskStatus: int { use AsStringSelectableEnumTrait; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsStringSelectableEnumTrait; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
stringOptions() — Generate a formatted string from enum cases
// First argument is the callback, second argument is glue TaskStatus::stringOptions(fn ($name, $value) => "$name => $value", ', '); // "INCOMPLETE => 0, COMPLETED => 1, CANCELED => 2"
For pure enums (non-backed), the name is used in place of $value (meaning that both $name and $value are the same).
Both arguments are optional. The default glue is \n and the default callback generates HTML <option> tags:
// <option value="0">Incomplete</option> // <option value="1">Completed</option> // <option value="2">Canceled</option> TaskStatus::stringOptions(); // backed enum // <option value="ADMINISTRATOR">Administrator</option> // <option value="Subscriber">Subscriber</option> // <option value="GUEST">Guest</option> Role::stringOptions(); // pure enum
PHPStan
To assist PHPStan when using invokable cases and metadata methods, include the PHPStan extension in your phpstan.neon:
includes: - ./vendor/ph-il/enums/extension.neon
Note
If you have phpstan/extension-installer installed, the extension is included automatically.
Development
Run all checks locally:
castor ci:all
Code style will be automatically fixed by php-cs-fixer.
Todo
- Version 0.3.0
- Add operations
- count
- Add Filtering and Sorting
- first
- with or without filter via callback
- Add operations
License
MIT. See LICENSE for details.