Skip to content

Conversation

@binaryfire
Copy link
Contributor

feat(session): add BackedEnum support for session keys

Summary

This PR enables using BackedEnums as session keys, building on the Str::from() and Str::fromAll() helpers merged in #291. Session keys are a prime candidate for enum support since they're frequently used string identifiers prone to typos.

Motivation

Using string literals for session keys is error-prone:

// Typos slip through unnoticed
session()->get('user_prferences');  // 'prferences' typo
session()->put('curent_user', $user);  // 'curent' typo

// Inconsistent naming causes bugs
session()->put('currentUser', $user);
session()->get('current_user');  // Different key!

BackedEnums solve this with IDE autocomplete and compile-time safety:

enum SessionKey: string
{
    case CurrentUser = 'current_user';
    case CartItems = 'cart_items';
    case Locale = 'locale';
}

// Now you get autocomplete, refactoring support, and typo prevention
session()->put(SessionKey::CurrentUser, $user);
session()->get(SessionKey::CurrentUser);

Usage Examples

Basic operations:

use App\Enums\SessionKey;

// Get and put
session()->put(SessionKey::CurrentUser, $user);
$user = session()->get(SessionKey::CurrentUser);

// With defaults
$theme = session()->get(SessionKey::Theme, 'light');

// Pull (get and remove)
$items = session()->pull(SessionKey::CartItems);

Checking existence:

// Single key
if (session()->has(SessionKey::CurrentUser)) {
    // ...
}

// Multiple keys - all must exist
if (session()->exists([SessionKey::CurrentUser, SessionKey::CartItems])) {
    // ...
}

// Any key exists
if (session()->hasAny([SessionKey::CurrentUser, 'legacy_key'])) {
    // ...
}

Array operations with mixed enums and strings:

// Get only specific keys
$data = session()->only([SessionKey::CurrentUser, SessionKey::CartItems, 'legacy_key']);

// Get all except specific keys
$data = session()->except([SessionKey::InternalFlag]);

// Forget multiple keys
session()->forget([SessionKey::CartItems, SessionKey::Locale, 'temp_data']);

Flash data:

// Flash data for the next request
session()->flash(SessionKey::WizardStep, 3);

// Flash data for current request only
session()->now(SessionKey::DebugInfo, $debugData);

Increment/decrement:

session()->increment(SessionKey::PageViews);
session()->decrement(SessionKey::RemainingAttempts);

Remember pattern:

$settings = session()->remember(SessionKey::UserSettings, function () {
    return $this->loadExpensiveSettings();
});

Interoperability - enum and string access the same data:

// These all access the same underlying data
session()->put(SessionKey::CurrentUser, $user);
session()->get('current_user');  // Same value
session()->has(SessionKey::CurrentUser);  // true
session()->forget('current_user');  // Removes it

Int-backed enums work too:

enum CacheFlag: int
{
    case Enabled = 1;
    case Disabled = 0;
}

session()->put(CacheFlag::Enabled, true);
session()->get(CacheFlag::Enabled);  // true

Methods Updated

Method Signature Change
get() BackedEnum|string $key
put() array|BackedEnum|string $key
pull() BackedEnum|string $key
remove() BackedEnum|string $key
forget() array|BackedEnum|string $keys
exists() array|BackedEnum|string $key
missing() array|BackedEnum|string $key
has() array|BackedEnum|string $key
hasAny() array|BackedEnum|string $key
remember() BackedEnum|string $key
push() BackedEnum|string $key
increment() BackedEnum|string $key
decrement() BackedEnum|string $key
flash() BackedEnum|string $key
now() BackedEnum|string $key
only() Array elements accept enums
except() Array elements accept enums
hasOldInput() BackedEnum|string|null $key
getOldInput() BackedEnum|string|null $key

Breaking Changes

None. This is purely additive - existing code using string keys continues to work unchanged.

Tests

Added 41 new tests covering:

  • Single enum keys for all operations
  • Arrays of enums
  • Mixed arrays (enums + strings)
  • Int-backed enum support
  • Flash data lifecycle with enums
  • Interoperability between enum and string access

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.

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