A collection of enum helpers for PHP.
You can read more about the idea on Twitter. I originally wanted to include the InvokableCases
helper in archtechx/helpers
, but it makes more sense to make it a separate dependency and use it inside the other package.
PHP 8.1+ is required.
composer require archtechx/enums
This helper lets you get the value of a backed enum, or the name of a pure enum, by "invoking" it โ either statically (MyEnum::FOO()
instead of MyEnum::FOO
), 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 ()
:
MyEnum::FOO; // => MyEnum instance
MyEnum::FOO(); // => 1
use ArchTech\Enums\InvokableCases;
enum TaskStatus: int
{
use InvokableCases;
case INCOMPLETE = 0;
case COMPLETED = 1;
case CANCELED = 2;
}
enum Role
{
use InvokableCases;
case ADMINISTRATOR;
case SUBSCRIBER;
case GUEST;
}
TaskStatus::INCOMPLETE(); // 0
TaskStatus::COMPLETED(); // 1
TaskStatus::CANCELED(); // 2
Role::ADMINISTRATOR(); // 'ADMINISTRATOR'
Role::SUBSCRIBER(); // 'SUBSCRIBER'
Role::GUEST(); // 'GUEST'
public function updateStatus(TaskStatus $status, Role $role)
{
$this->record->setStatus($status(), $role());
}
This helper returns a list of case names in the enum.
use ArchTech\Enums\Names;
enum TaskStatus: int
{
use Names;
case INCOMPLETE = 0;
case COMPLETED = 1;
case CANCELED = 2;
}
enum Role
{
use Names;
case ADMINISTRATOR;
case SUBSCRIBER;
case GUEST;
}
TaskStatus::names(); // ['INCOMPLETE', 'COMPLETED', 'CANCELED']
Role::names(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
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)
use ArchTech\Enums\Values;
enum TaskStatus: int
{
use Values;
case INCOMPLETE = 0;
case COMPLETED = 1;
case CANCELED = 2;
}
enum Role
{
use Values;
case ADMINISTRATOR;
case SUBSCRIBER;
case GUEST;
}
TaskStatus::values(); // [0, 1, 2]
Role::values(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
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).
use ArchTech\Enums\Options;
enum TaskStatus: int
{
use Options;
case INCOMPLETE = 0;
case COMPLETED = 1;
case CANCELED = 2;
}
enum Role
{
use Options;
case ADMINISTRATOR;
case SUBSCRIBER;
case GUEST;
}
TaskStatus::options(); // ['INCOMPLETE' => 0, 'COMPLETED' => 1, 'CANCELED' => 2]
Role::options(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
This helper adds from()
and tryFrom()
to pure enums, and adds fromName()
and tryFromName()
to all enums.
BackedEnum
instances already implement their ownfrom()
andtryFrom()
methods, which will not be overridden by this trait. Attempting to override those methods in aBackedEnum
causes a fatal error.- Pure enums only have named cases and not values, so the
from()
andtryFrom()
methods are functionally equivalent tofromName()
andtryFromName()
use ArchTech\Enums\From;
enum TaskStatus: int
{
use From;
case INCOMPLETE = 0;
case COMPLETED = 1;
case CANCELED = 2;
}
enum Role
{
use From;
case ADMINISTRATOR;
case SUBSCRIBER;
case GUEST;
}
Role::from('ADMINISTRATOR'); // Role::ADMINISTRATOR
Role::from('NOBODY'); // Error: ValueError
Role::tryFrom('GUEST'); // Role::GUEST
Role::tryFrom('NEVER'); // null
TaskStatus::fromName('INCOMPLETE'); // TaskStatus::INCOMPLETE
TaskStatus::fromName('MISSING'); // Error: ValueError
Role::fromName('SUBSCRIBER'); // Role::SUBSCRIBER
Role::fromName('HACKER'); // Error: ValueError
TaskStatus::tryFromName('COMPLETED'); // TaskStatus::COMPLETED
TaskStatus::tryFromName('NOTHING'); // null
Role::tryFromName('GUEST'); // Role::GUEST
Role::tryFromName('TESTER'); // null
This trait lets you add metadata to enum cases.
use ArchTech\Enums\Metadata;
use ArchTech\Enums\Meta\Meta;
use App\Enums\MetaProperties\{Description, 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;
}
Explanation:
Description
andColor
are 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)
TaskStatus::INCOMPLETE->description(); // 'Incomplete Task'
TaskStatus::COMPLETED->color(); // 'green'
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'
TaskStatus::fromMeta(Color::make('green')); // TaskStatus::COMPLETED
TaskStatus::fromMeta(Color::make('blue')); // Error: ValueError
TaskStatus::tryFromMeta(Color::make('green')); // TaskStatus::COMPLETED
TaskStatus::tryFromMeta(Color::make('blue')); // null
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.
To assist PHPStan when using invokable cases, you can include the PHPStan extensions into your own phpstan.neon
file:
includes:
- ./vendor/archtechx/enums/extension.neon
Run all checks locally:
./check
Code style will be automatically fixed by php-cs-fixer.