ph-il/enums

Zero-dependencies package to supercharge enum functionalities via helper traits.

Maintainers

Package info

github.com/parler-haut-interagir-librement/enums

Homepage

pkg:composer/ph-il/enums

Statistics

Installs: 16

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.2.0 2026-03-01 22:55 UTC

This package is auto-updated.

Last update: 2026-03-01 22:58:35 UTC


README

Author PHP Version Latest Version Software License Build Status Code Quality Coverage PHPStan Level Total Downloads

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

Table of Contents

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:

  • BackedEnum instances already implement their own from() and tryFrom() methods, which will not be overridden by this trait.
  • For pure enums, from() and tryFrom() are functionally equivalent to fromName() and tryFromName().

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:

  • Description is an exemple that we do provide as a class attributes for meta properties
  • Color is 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

License

MIT. See LICENSE for details.