fab2s/enumerate

Enumerate, a nice boost to your enums

Installs: 849

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 1

Forks: 0

Open Issues: 0

pkg:composer/fab2s/enumerate

0.0.1 2025-03-16 14:44 UTC

This package is auto-updated.

Last update: 2026-01-22 01:28:48 UTC


README

QA CI codecov Packagist Version PRs Welcome License

A lightweight PHP library that makes native enums practical for real-world applications.

The Problem

PHP 8.1 enums are powerful but have limitations that create friction in production code:

  • No Stringable support - Can't use enums directly in string contexts
  • Strict type restrictions - Can't safely instantiate from mixed input (HTTP requests, databases)
  • No inheritance - Can't share behavior across related enums
  • Type mismatch pain - IntBackedEnum::tryFrom('1') throws instead of returning null

The Solution

Enumerate provides a unified API to work with any enum type consistently:

// Safely instantiate from any input type
$status = Status::tryFromAny($request->get('status')); // string, int, null, or enum instance

// Get a serializable value from any enum
$value = $status->toValue(); // Works for Unit, String, and Int backed enums

// Compare across enum boundaries
$status->compares(LegacyStatus::Active); // true if values match

Installation

composer require fab2s/enumerate

Requirements: PHP 8.1+

Quick Start

Add the trait to your enum:

use fab2s\Enumerate\EnumerateTrait;
use fab2s\Enumerate\EnumerateInterface;

enum Status: string implements EnumerateInterface
{
    use EnumerateTrait;

    case Pending = 'pending';
    case Active  = 'active';
    case Closed  = 'closed';
}

Now you can:

// From HTTP request (could be string, int, or null)
$status = Status::tryFromAny($request->input('status'));

// From database or JSON
$status = Status::fromAny($row['status']); // throws if invalid

// To database or JSON
$value = $status->toValue(); // 'active'
json_encode($status);        // '"active"'

// Validation
$status->equals('active', 'pending'); // true if matches any

API Reference

Instantiation Methods

Method Returns Throws Description
tryFromAny($value, $strict = true) ?static - Safe instantiation from any type
fromAny($value, $strict = true) static InvalidArgumentException Strict instantiation
tryFromName($name) ?static - Match by case name
fromName($name) static InvalidArgumentException Strict match by case name

Value Methods

Method Returns Description
toValue() int|string Get the backed value (or case name for UnitEnum)
jsonSerialize() int|string Same as toValue(), implements JsonSerializable

Comparison Methods

Method Returns Description
equals(...$values) bool Strict match against provided values
compares(...$values) bool Loose match, allows cross-enum comparison by value

Type Inspection (Static Helper Only)

Method Returns Description
Enumerate::getType($enum) ?string Returns 'string', 'int', or null
Enumerate::isStringBacked($enum) bool Check if string-backed
Enumerate::isIntBacked($enum) bool Check if int-backed
Enumerate::isBacked($enum) bool Check if backed (not UnitEnum)

Detailed Usage

Safe Instantiation with tryFromAny

The native tryFrom() only accepts the exact backing type. tryFromAny() handles real-world input:

enum Priority: int implements EnumerateInterface
{
    use EnumerateTrait;

    case Low    = 1;
    case Medium = 2;
    case High   = 3;
}

// All return Priority::Medium
Priority::tryFromAny(2);
Priority::tryFromAny(Priority::Medium);

// All return null (no exception)
Priority::tryFromAny('2');     // string on int-backed
Priority::tryFromAny(null);
Priority::tryFromAny('invalid');

Cross-Enum Matching

When migrating between enum versions or comparing related enums, use non-strict mode:

enum LegacyRole: string {
    case Admin = 'admin';
    case User  = 'user';
}

enum Role: string implements EnumerateInterface
{
    use EnumerateTrait;

    case Admin     = 'admin';
    case User      = 'user';
    case Moderator = 'moderator';
}

// Strict mode (default): only accepts same enum type
Role::tryFromAny(LegacyRole::Admin);              // null
Role::tryFromAny(LegacyRole::Admin, strict: true); // null

// Non-strict mode: matches by value across enums
Role::tryFromAny(LegacyRole::Admin, strict: false); // Role::Admin

Working with UnitEnums

UnitEnums (no backing value) are matched by case name:

enum Color implements EnumerateInterface
{
    use EnumerateTrait;

    case Red;
    case Green;
    case Blue;
}

Color::tryFromAny('Red');   // Color::Red
Color::tryFromName('Red');  // Color::Red
Color::Red->toValue();      // 'Red' (case name as string)
json_encode(Color::Red);    // '"Red"'

Comparison Methods

enum Status: string implements EnumerateInterface
{
    use EnumerateTrait;

    case Draft     = 'draft';
    case Published = 'published';
    case Archived  = 'archived';
}

$status = Status::Published;

// equals: strict comparison, multiple values allowed
$status->equals('published');                    // true
$status->equals('draft', 'published');           // true (matches any)
$status->equals(Status::Published);              // true

// compares: allows cross-enum matching by value
$status->compares(OtherEnum::Published);         // true if values match

Using the Static Helper

All methods are also available via the Enumerate static class, useful when you can't modify the enum:

use fab2s\Enumerate\Enumerate;

// Works with any enum, even without the trait
Enumerate::tryFromAny(SomeEnum::class, $value);
Enumerate::toValue(SomeEnum::Case);
Enumerate::equals(SomeEnum::Case, 'value');
Enumerate::getType(SomeEnum::class); // 'string', 'int', or null

Real-World Examples

Form Request Handling

class UpdateOrderRequest extends FormRequest
{
    public function validated(): array
    {
        return [
            'status'   => OrderStatus::fromAny($this->input('status')),
            'priority' => Priority::tryFromAny($this->input('priority')) ?? Priority::Medium,
        ];
    }
}

Database Model Casting

class Order extends Model
{
    protected $casts = [
        'status' => OrderStatus::class,
    ];

    public function scopeActive($query)
    {
        return $query->whereIn('status', [
            OrderStatus::Pending->toValue(),
            OrderStatus::Processing->toValue(),
        ]);
    }
}

API Response

return response()->json([
    'status' => $order->status, // Automatically serialized via JsonSerializable
]);

Why This Exists

PHP enums prioritize type safety over practicality. While philosophically sound, this creates boilerplate in real applications where enums must interoperate with HTTP requests (always strings), databases, and JSON APIs.

Enumerate handles this complexity internally so you don't have to write defensive code everywhere enums cross system boundaries.

Contributing

Contributions are welcome. Please open issues and submit pull requests.

License

Enumerate is open-source software licensed under the MIT license.