Skip to content

Conversation

@binaryfire
Copy link
Contributor

@binaryfire binaryfire commented Dec 22, 2025

This PR ports Laravel's comprehensive enum_value() support throughout the framework, allowing enums to be used in place of strings across caching, queues, broadcasting, scheduling, validation, and more.

Summary

Laravel has gradually added enum support across the entire framework, allowing developers to use type-safe enums instead of magic strings for things like queue names, cache keys, broadcast channels, and more. This PR brings that same level of enum support to Hypervel, and builds on it.

The enum_value() helper function extracts the underlying value from enums:

  • BackedEnum: Returns ->value (string or int)
  • UnitEnum: Returns ->name (string)
  • Non-enum: Returns the value unchanged

Ported from Laravel

Cache

  • Repository: has(), missing(), get(), pull(), put(), set(), add(), increment(), decrement(), forever(), remember(), sear(), rememberForever(), forget(), delete()
  • RateLimiter: for(), limiter(), tooManyAttempts(), hit(), attempts(), resetAttempts(), remaining(), retriesLeft(), clear(), availableIn(), cleanRateLimiterKey()

Queue & Bus

  • Queueable trait: onConnection(), onQueue(), allOnConnection(), allOnQueue()
  • PendingDispatch: onConnection(), onQueue(), afterCommit(), beforeCommit()
  • PendingBatch: onQueue(), onConnection()
  • PendingChain: onQueue(), onConnection()
  • RateLimited middleware: Constructor accepts enum limiter names

Broadcasting

  • InteractsWithBroadcasting trait: broadcastOn()
  • PendingBroadcast: onQueue(), onConnection()

Scheduling

  • ManagesFrequencies trait: days(), weekdays(), weekends(), sundays() through saturdays()
  • Schedule: job() accepts enum queue/connection

Database & Eloquent

  • Query\Builder: from(), fromSub(), join() and variants, crossJoinSub(), orderBy(), latest(), oldest(), groupBy(), dd(), dump()
  • Model: setTable()
  • Pivot / MorphPivot: setTable()
  • Factory: connection(), recycle()

Auth & Gate

  • Gate: allows(), denies(), check(), any(), none(), authorize(), inspect(), raw(), define(), resource(), policy()
  • AuthorizesRequests trait: authorize(), authorizeForUser(), authorizeResource(), can()
  • Authorize middleware: Enum abilities support

Session

  • Store / Session contract: get(), has(), put(), remember(), push(), increment(), decrement(), forget(), remove(), pull()

Events

  • EventDispatcher: dispatch(), listen(), hasListeners(), push(), flush(), forget()
  • QueuedClosure: onConnection(), onQueue()

Validation

  • Rule::in(), Rule::notIn(): Accept enum values
  • In / NotIn rules: Constructor accepts enums

Filesystem

  • FilesystemManager: disk(), drive(), build(), set(), forgetDisk(), purge()
  • Storage facade: fake(), persistentFake()

Router

  • ThrottleRequests middleware: using() accepts enum limiter names

Redis

  • Redis facade: connection()

Translation

  • Translator: get(), choice(), has(), hasForLocale()

Support

  • Collection / LazyCollection: contains(), doesntContain(), containsStrict()
  • InteractsWithData trait: data(), has(), filled(), isNotFilled(), missing(), hasAny()
  • Js: Enum value serialization (changed from BackedEnum to UnitEnum)

Helpers

  • report(): Exception context accepts enum keys

Extra Additions (Hypervel-specific)

Laravel currently doesn't support enums for these things, but probably will in the future:

Cache Tags

  • TaggedCache: increment(), decrement()
  • RedisTaggedCache: add(), put(), increment(), decrement(), forever()

Context

  • Context: set(), get(), has(), destroy(), override(), getOrSet()

Cookie

  • CookieManager / Cookie contract: has(), get(), make(), expire(), unqueue(), forever(), forget()

Sanctum

Updated Sanctum to use enum_value() instead of Str::from() / Str::fromAll() for consistency:

  • HasApiTokens trait: tokenCan(), tokenCant(), createToken()
  • PersonalAccessToken / TransientToken: can(), cant()
  • Replaced Str::from() / Str::fromAll() with enum_value() / array_map(enum_value(...))

Type Signature Improvements

Laravel doesn't use signature type hints to avoid breaking backwards compatibility. Since this will be merged in 0.4, we can make it a breaking change and use modern typing:

  • Moved type hints from docblocks to signatures
  • Updated BackedEnum to UnitEnum in type hints to match Laravel's pattern (UnitEnum is the base interface that BackedEnum extends)
  • Removed redundant BackedEnum|UnitEnum union types (simplified to just UnitEnum since BackedEnum extends UnitEnum)
  • Affected files: Queueable, PendingDispatch, PendingBatch, PendingChain, In, NotIn, Js, Sanctum traits/contracts

Simplified enum_value() Implementation

Removed unnecessary transform() wrapper to match Laravel's simpler implementation:

// Before
return transform($value, fn ($value) => match (true) {
    $value instanceof BackedEnum => $value->value,
    $value instanceof UnitEnum => $value->name,
    default => $value,
}, $default ?? $value);

// After
return match (true) {
    $value instanceof BackedEnum => $value->value,
    $value instanceof UnitEnum => $value->name,
    default => $value ?? value($default),
};

`transform()` wasn't adding any value and added unnecessary overhead.

Test Coverage

Added comprehensive tests for all enum support:

  • String-backed enum tests (returns ->value)
  • Unit enum tests (returns ->name)
  • Int-backed enum tests (verifies TypeError at typed boundaries where appropriate)

Enable using BackedEnums as session keys across all session methods,
leveraging the Str::from() and Str::fromAll() helpers for normalization.

Methods updated:
- get, put, pull, remove, forget
- exists, missing, has, hasAny
- remember, push, increment, decrement
- flash, now
- only, except
- hasOldInput, getOldInput

Includes 41 new tests covering single enums, arrays of enums,
mixed arrays (enums + strings), and int-backed enum support.
@albertcht albertcht added the breaking-change Breaking changes label Dec 24, 2025
@albertcht
Copy link
Member

albertcht commented Dec 24, 2025

Hi @binaryfire, thank you for this pull request! Will this PR introduce breaking changes for anyone who has implemented custom session stores? Custom implementations of the session store interface will need to update their method signatures to accept additional BackedEnum parameter.

Therefore, I'll merge it into the upcoming v0.4 branch, which we expect to release soon.

@binaryfire
Copy link
Contributor Author

@albertcht Ah yeah, good point. No problems! I'm going to make PRs for the other places where enum support would be useful. This what I'm thinking:

  • Cache (keys and tags)
  • Config
  • RateLimiter
  • Gate
  • Cookie
  • Context

What do you think?

@albertcht
Copy link
Member

@albertcht Ah yeah, good point. No problems! I'm going to make PRs for the other places where enum support would be useful. This what I'm thinking:

  • Cache (keys and tags)
  • Config
  • RateLimiter
  • Gate
  • Cookie
  • Context

What do you think?

I think enum support is a great idea, but we could be more strategic about where it provides the most value.

Enums work best when the string values are complete, standalone identifiers that are used as-is throughout the application. They're ideal for scenarios where you have a fixed, predefined set of values that get repeatedly referenced across multiple layers (controllers, services, views, middleware) without modification.

Enums are a poor fit when string values need concatenation with dynamic parts or represent hierarchical structures. For example, patterns like "user:profile:{$id}" or "posts:{$category}:{$page}" where you're constantly appending variables defeat the purpose – you can only define prefixes in enums and still need string concatenation. Similarly, dot-notation hierarchical paths like "database.connections.mysql.host" create awkward enum case names (DATABASE_CONNECTIONS_MYSQL_HOST) that are verbose and hard to maintain, especially when you have hundreds of possible paths including unpredictable third-party additions.

From your list, I think Gate seems to be the most suitable choice, while others like Cache, Config, Context, and RateLimiter don't seem to be good fits.

@binaryfire binaryfire changed the title feat(session): add BackedEnum support for session keys feat: add BackedEnum support to more places in the framework Dec 25, 2025
@binaryfire binaryfire marked this pull request as draft December 25, 2025 21:49
@binaryfire
Copy link
Contributor Author

binaryfire commented Jan 17, 2026

Hi @albertcht. Laravel has added BackedEnum support for cache keys: laravel/framework#58246

There have have been several PRs for adding enum support across the framework and they've all been accepted:

laravel/framework#58241
laravel/framework#58343
laravel/framework#57190
laravel/framework#58310

The RateLimiter supports enums now too: https://github.com/laravel/framework/blob/3026dc7a7cf24b413a34c14ea3da992d045cac66/src/Illuminate/Cache/RateLimiter.php#L318

I know you said we should limit the places enums are supported, but I think we should follow Laravel's lead and add support to the following things:

Already supported by Laravel:

  • Cache
  • RateLimiter
  • Gate
  • Session
  • Translation replacement values
  • Collections

Not supported by Laravel yet, but I'm sure they will be soon:

  • Cookie
  • Context

What do you think? I use enums for all these things. Not in every situation, but whenever possible. There's no performance overhead and it gives developers the option of better type safety if they want it.

I'll port everything over plus add support in a few more places I think make sense, then you can take a look.

- Add UnitEnum support to session contract and store (BackedEnum uses
  ->value, UnitEnum uses ->name)
- Replace Str::from()/fromAll() with enum_value() to match Laravel
- Simplify enum_value() helper to match Laravel's direct implementation
  (remove unnecessary transform() wrapper)
- Add comprehensive UnitEnum test coverage (24 new tests)
- Update all session method signatures with strict UnitEnum types

BREAKING CHANGE: Session contract method signatures now include UnitEnum
type. Custom implementations must update their signatures.
- Add enum support to for() and limiter() methods for named rate limiters
- Add resolveLimiterName() helper using enum_value()
- Add comprehensive enum tests covering BackedEnum, UnitEnum, and
  string interoperability

Following Laravel's approach where only named limiter methods support
enums (for/limiter), not key-based methods (attempt/hit/etc) since
those typically use dynamic concatenated keys.
- Update Gate contract with enum type hints for has(), define(), allows(),
  denies(), check(), any(), none(), authorize(), and inspect() methods
- Update Gate implementation to use enum_value() for ability resolution
- Update Authorize middleware to support enums in using() method
- Update AuthorizesRequests trait to use enum_value() in parseAbilityAndArguments()
- Add comprehensive GateEnumTest with tests for all enum scenarios
- Override groupBy() to convert UnitEnum/Stringable keys via enum_value()
- Override keyBy() to convert UnitEnum keys via enum_value()
- Override getArrayableItems() to wrap UnitEnum as [$enum] instead of casting
- Override operatorForWhere() to use enum_value() for comparisons
- Add Collection.php to phpstan ignored paths (same as Eloquent Collection)
- Add comprehensive tests for both enum and base functionality
Add enum support to Cache Repository key methods (get, put, add, increment,
decrement, forever, forget, remember, etc.) and their child classes
(TaggedCache, RedisTaggedCache). Also adds enum support for cache tags,
allowing enums to be used as tag names - a feature Laravel doesn't have yet.
Allow using enums as context keys for set, get, has, destroy, override,
and getOrSet methods. Follows the same pattern as Session enum support.
Allow using enums as replacement values in translations. The enum's value
(for BackedEnum) or name (for UnitEnum) is used as the replacement string.
…nd Queue

- Redis::connection() now accepts UnitEnum for connection names
- FilesystemManager::disk()/drive() now accepts UnitEnum for disk names
- Model::setConnection() now accepts UnitEnum for connection names
- Factory::connection() now accepts UnitEnum for connection names
- Pivot::setConnection() now accepts UnitEnum for connection names
- MorphPivot::setConnection() now accepts UnitEnum for connection names
- Queueable::onConnection/onQueue() changed from BackedEnum to UnitEnum for consistency
BackedEnum extends UnitEnum, so including both in union types is
redundant. Simplified all BackedEnum|UnitEnum unions to just UnitEnum.
Since BackedEnum extends UnitEnum, having both in union types is
redundant. This removes BackedEnum from union types in validation,
permission, queue, and cache packages while keeping the import
where needed for instanceof checks.
Cast enum_value() results to string where underlying methods expect
string parameters. This ensures int-backed enums (e.g., enum Foo: int)
work correctly by converting their int values to strings.

Fixed locations:
- Context: 6 parent method calls expecting string $id
- Gate: raw() call expecting string ability
- FilesystemManager: disk name resolution
- Redis: connection name resolution
- PendingBatch/PendingChain: queue name (with null handling)
- Cache Repository: increment/decrement/forever and event dispatch
- TaggedCache: increment/decrement with itemKey
- RedisTaggedCache: all key operations
- Session Store: Arr::pull() calls requiring string keys

Added int-backed enum tests for Context, Cache, Gate, Filesystem, and
Redis to verify the casts work correctly.
- Schedule: job() accepts UnitEnum for queue/connection, useCache() for store
- ManagesFrequencies: timezone() accepts UnitEnum
- QueuedClosure: onConnection(), onQueue(), onGroup() accept UnitEnum
- EventDispatcher: queue names support UnitEnum in queueHandler()
- InteractsWithBroadcasting: broadcastVia() accepts UnitEnum
- PendingBroadcast: via() accepts UnitEnum
- Collection: lazy() returns Hypervel's LazyCollection for enum support
- LazyCollection: countBy() supports enum group keys

All use (string) enum_value() to handle int-backed enums correctly.
- InteractsWithData: date() timezone param accepts UnitEnum
- Query/Builder: castBinding() supports UnitEnum (not just BackedEnum)

Both use enum_value() for consistent enum handling.
Both helpers now accept UnitEnum for timezone parameter and use
enum_value() for consistent enum handling.
Let int-backed enums fail with TypeError at typed boundaries instead of
silently converting to strings like "1". This matches Laravel's behavior
where enum_value() returns the raw value and type mismatches fail naturally.

Also adds enum tests for TaggedCache, RedisTaggedCache, and RateLimited.
Replace Str::from() and Str::fromAll() with enum_value() for consistency
with other enum-supporting APIs across the codebase. Also updates type
hints from BackedEnum to UnitEnum to match the established pattern.
- Cookie: has(), get(), make(), expire(), unqueue(), forever(), forget()
- Js: Changed BackedEnum to UnitEnum, use enum_value()
- ThrottleRequests::using(): Accept enum limiter names
- PendingBatch/PendingChain::onConnection(): Accept enum connection names
- Updated facade docblocks via facade-documenter
- Added comprehensive enum tests
@binaryfire binaryfire changed the title feat: add BackedEnum support to more places in the framework feat: port framework enum support from Laravel Jan 18, 2026
@binaryfire binaryfire marked this pull request as ready for review January 18, 2026 09:58
@binaryfire
Copy link
Contributor Author

Hi @albertcht. This PR is ready for review. I've updated the post. Most of this is just porting enum support from Laravel. I added enum support in a few additional areas as well.

Let me know what you think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants