ph-il / enums
Zero-dependencies package to supercharge enum functionalities via helper traits.
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/ph-il/enums
Requires
- php: ^8.1
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.8
- 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: ^11.5
- rector/rector: ^2.2
README
A collection of enum helpers for PHP.
Tip
Need to supercharge enums in a Symfony application?
Consider using Enums Bundle instead.
A collection of enum helpers for PHP.
AsInvocableEnumAsNameableEnumAsValuableEnumAsSelectableEnumAsComparableEnumAsFromambleEnumAsMetadableEnumAsStringSelectableEnum
Table of Contents
Installation
PHP 8.1+ 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
This helper lets you 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()).
That way, you can use enums as array keys:
'statuses' => [ TaskStatus::INCOMPLETE() => ['some configuration'], TaskStatus::COMPLETED() => ['some configuration'], ],
Or access the underlying primitives for any other use cases:
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
This helper 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
This helper returns a list of case values for backed enums, or a list of case names for pure enums (making this functionally equivalent to ::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
This helper returns an associative array of case names and values for backed enums, or a list of names for pure enums (making this functionally equivalent to ::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
This trait lets you compare enums using is(), isNot(), in() and notIn().
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; }
Use the is() method
TaskStatus::INCOMPLETE->is(TaskStatus::INCOMPLETE); // true TaskStatus::INCOMPLETE->is(TaskStatus::COMPLETED); // false Role::ADMINISTRATOR->is(Role::ADMINISTRATOR); // true Role::ADMINISTRATOR->is(Role::NOBODY); // false
Use the isNot() method
TaskStatus::INCOMPLETE->isNot(TaskStatus::INCOMPLETE); // false TaskStatus::INCOMPLETE->isNot(TaskStatus::COMPLETED); // true Role::ADMINISTRATOR->isNot(Role::ADMINISTRATOR); // false Role::ADMINISTRATOR->isNot(Role::NOBODY); // true
Use the in() method
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
Use the notIn() method
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
AsFromambleEnum
This helper 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. Attempting to override those methods in aBackedEnumcauses a fatal error.- Pure enums only have named cases and not values, so the
from()andtryFrom()methods are functionally equivalent tofromName()andtryFromName()
Apply the trait on your enum
use Phil\Enums\AsFromambleEnum; enum TaskStatus: int { use AsFromambleEnum; case INCOMPLETE = 0; case COMPLETED = 1; case CANCELED = 2; } enum Role { use AsFromambleEnum; case ADMINISTRATOR; case SUBSCRIBER; case GUEST; }
Use the from() method
Role::from('ADMINISTRATOR'); // Role::ADMINISTRATOR Role::from('NOBODY'); // Error: ValueError
Use the tryFrom() method
Role::tryFrom('GUEST'); // Role::GUEST Role::tryFrom('NEVER'); // null
Use the fromName() method
TaskStatus::fromName('INCOMPLETE'); // TaskStatus::INCOMPLETE TaskStatus::fromName('MISSING'); // Error: ValueError Role::fromName('SUBSCRIBER'); // Role::SUBSCRIBER Role::fromName('HACKER'); // Error: ValueError
Use the tryFromName() method
TaskStatus::tryFromName('COMPLETED'); // TaskStatus::COMPLETED TaskStatus::tryFromName('NOTHING'); // null Role::tryFromName('GUEST'); // Role::GUEST Role::tryFromName('TESTER'); // null
AsMetadableEnum
This trait lets you add metadata to enum cases.
Apply the trait on your enum
use App\Enums\MetaProperties\Color; use Phil\Enums\AsMetadableEnum; use Phil\Enums\Attribute\Description; use Phil\Enums\Attribute\Meta; #[Meta(Description::class, Color::class)] enum TaskStatus: int { use AsMetadableEnum; #[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]call enables those two meta properties on the enum - Each case must have a defined description & color (in this example)
Access the metadata
TaskStatus::INCOMPLETE->description(); // 'Incomplete Task' TaskStatus::COMPLETED->color(); // 'green'
Creating meta properties
Each meta property (= attribute used on a case) needs to exist as a class.
#[Attribute] class Color extends MetaProperty {} #[Attribute] class Description extends MetaProperty {}
Inside the class, you can customize a few things. 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 MetaProperty { public static function method(): string { return 'note'; } }
With the code above, the description of a case will be accessible as TaskStatus::INCOMPLETE->note().
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'
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.
Use the fromMeta() method
TaskStatus::fromMeta(Color::make('green')); // TaskStatus::COMPLETED TaskStatus::fromMeta(Color::make('blue')); // Error: ValueError
Use the tryFromMeta() method
TaskStatus::tryFromMeta(Color::make('green')); // TaskStatus::COMPLETED TaskStatus::tryFromMeta(Color::make('blue')); // null
Included Meta Properties
Description
Group
Recommendation: use annotations and traits
If you'd like to add better IDE support for the metadata getter methods, you can use @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; }
And if you're using the same meta property in multiple enums, you can create a dedicated trait that includes this @method annotation.
AsStringSelectableEnum
The trait adds the stringOptions() method that can be used for generating convenient string representations of your enum options.
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()
The trait adds the stringOptions() method that can be used for generating convenient string representations of your enum options:
// First argument is the callback, second argument is glue // returns "INCOMPLETE => 0, COMPLETED => 1, CANCELED => 2" TaskStatus::stringOptions(fn ($name, $value) => "$name => $value", ', ');
For pure enums (non-backed), the name is used in place of $value (meaning that both $name and $value are the same).
Both arguments for this method are optional, the glue defaults to \n and the callback defaults to generating 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, you can include the PHPStan extensions into your own phpstan.neon file:
includes: - ./vendor/ph-il/enums/extension.neon
Note: If you have installed phpstan/extension-installer, the extension is automatically included.
Development
Run all checks locally:
./check
Code style will be automatically fixed by php-cs-fixer.
Todo
- Version 0.2.0
- Tests
- Add Castor and all lints/scans used by ph-il projects
- Add self-awareness
- isPure
- isBacked
- isBackedBy
- Integer
- String
- Version 0.3.0
- Add operations
- count
- first
- with or without filter via callback
- Add Filtering and Sorting
- Add operations