Skip to content

Conversation

@binaryfire
Copy link
Contributor

@binaryfire binaryfire commented Dec 14, 2025

I’m not sure if Str is the right place for this. Another option would be to create a separate helper.

Summary

Adds two new methods to the Str helper class that normalize BackedEnum|string|int values to strings. This provides a foundation for introducing enum support across framework APIs that only accept string identifiers.

Motivation

Many framework APIs currently only accept strings: cache tags, session keys, Sanctum token abilities, config keys, etc. Using string literals for these is error-prone:

// Typos slip through unnoticed

  Cache::tags(['uers', 'posts'])->flush();  // 'uers' typo
  session()->get('user_prferences');         // 'prferences' typo

  // Inconsistent casing causes bugs
  $user->tokenCan('Posts:Read');  // Should be 'posts:read'

BackedEnums solve this beautifully—IDE autocomplete, refactoring support, and compile-time safety:

  enum CacheTag: string {
      case Users = 'users';
      case Posts = 'posts';
  }

  enum SessionKey: string {
      case UserPreferences = 'user_preferences';
      case CartItems = 'cart_items';
  }

  enum TokenAbility: string {
      case PostsRead = 'posts:read';
      case PostsWrite = 'posts:write';
  }

Unfortunately you can't cast a BackedEnum to a string. This makes it awkward to add enum support to existing APIs. You need explicit instanceof checks and ->value access everywhere.

Solution

Str::from() provides a clean, consistent way to normalize enum-or-string values:

  use Hypervel\Support\Str;

  Str::from(CacheTag::Users);     // 'users'
  Str::from('legacy-tag');        // 'legacy-tag'
  Str::from(42);                  // '42'

  Str::fromAll([CacheTag::Users, 'legacy', CacheTag::Posts]);   // ['users', 'legacy', 'posts']

Enabling enum support across the framework

With this helper, adding enum support to framework APIs becomes simple. For example:

Cache tags:

  // In RedisTagSet constructor
  $this->names = Str::fromAll($names);

  // Users can now do:
  Cache::tags([CacheTag::Users, CacheTag::Posts])->put('key', $value);
  Cache::tags([CacheTag::Users])->flush();

Session keys:

  // In Session::get()
  $key = Str::from($key);

  // Users can now do:
  session()->get(SessionKey::UserPreferences);
  session()->put(SessionKey::CartItems, $items);

Sanctum token abilities:

 // In tokenCan() / can()
 $ability = Str::from($ability);

 // Users can now do:
 $user->tokenCan(TokenAbility::PostsRead);
 $request->user()->createToken('api', [TokenAbility::PostsRead, TokenAbility::PostsWrite]);

Config keys:

  // Users can now do:
  config(ConfigKey::AppName);
  Config::get(ConfigKey::MailDriver);

API

  /**
   * Get a string from a BackedEnum or return the value as-is.
   */
  Str::from(string|int|BackedEnum $value): string

  /**
   * Get strings from an array of BackedEnums or scalar values.
   */
  Str::fromAll(array $values): array

Design Decisions

  1. Lives on Str class — Follows existing Str::fromBase64() pattern. The Str:: prefix makes the output type clear.
  2. Always returns string — Int-backed enums are cast to string. This is intentional since all target use cases (cache tags, session keys, etc.) require strings.
  3. Accepts mixed arrays — fromAll() handles mixed enum/string arrays for backwards compatibility during migration.
  4. Minimal scope — Only handles string|int|BackedEnum. Other types (Stringable, pure enums, null) are intentionally excluded as edge cases better handled explicitly.

Tests

17 tests covering:

  • String passthrough
  • Int to string casting
  • String-backed enums
  • Int-backed enums (explicit assertIsString verification)
  • Mixed arrays
  • Empty arrays
  • Array key preservation
  • Data provider for edge cases

@albertcht
Copy link
Member

Hi @binaryfire, I think adding these two methods to the Str class would be very helpful. Regarding the format of the input parameter, have you considered also supporting float and Stringable?

@albertcht albertcht added the feature New feature or request label Dec 18, 2025
@albertcht albertcht changed the title Add Str::from() and Str::fromAll() helpers feat: add Str::from() and Str::fromAll() helpers Dec 18, 2025
@binaryfire
Copy link
Contributor Author

@Albert Yeah Stringable is a good idea. The main use case of these methods is normalizing mixed content, so it makes sense to include it.

Re: float, I'd suggest leaving it out. Float-to-string precision issues (0.1 + 0.2 → '0.30000000000000004') could cause subtle bugs. Floats don't naturally appear as identifiers like cache tags or session keys so I think it'd be better to let people handle those edge cases separately. But I'm happy to add it if you prefer. What do you think?

@binaryfire
Copy link
Contributor Author

Just added Stringable support. The test failure is unrelated.

@albertcht
Copy link
Member

@Albert Yeah Stringable is a good idea. The main use case of these methods is normalizing mixed content, so it makes sense to include it.

Re: float, I'd suggest leaving it out. Float-to-string precision issues (0.1 + 0.2 → '0.30000000000000004') could cause subtle bugs. Floats don't naturally appear as identifiers like cache tags or session keys so I think it'd be better to let people handle those edge cases separately. But I'm happy to add it if you prefer. What do you think?

@binaryfire, let's just exclude float as your suggestion.

@albertcht
Copy link
Member

there seems some network issues in github actions at this moment.

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

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants