Skip to content

[12.x] Add typed getters on Cache #58451

Merged
taylorotwell merged 4 commits intolaravel:12.xfrom
ahinkle:cache-typed-getters
Jan 30, 2026
Merged

[12.x] Add typed getters on Cache #58451
taylorotwell merged 4 commits intolaravel:12.xfrom
ahinkle:cache-typed-getters

Conversation

@ahinkle
Copy link
Contributor

@ahinkle ahinkle commented Jan 21, 2026

This PR adds typed getter methods to the cache repository, matching the pattern established in the config repository.

When retrieving values from the cache, static analysis tools struggle with the mixed return type, often requiring a mix of verbose annotations and manual type checks to get them to pass:

// Before
/** @var string|null $name */
$name = Cache::get('user:display_name');

if (! is_string($name)) {
    return 'Guest';
}

return $name;

Even with a default value, Cache::get() returns mixed to static analysis tools:

$name = Cache::get('user:display_name', 'Guest'); // PHPStan: "Parameter expects string, mixed given"

With typed getters, this becomes:

return Cache::string('user:display_name', 'Guest');

The same applies to other types - PHPStan requires a redundant type check:

// Before
// Option 1: Annotate the type (no runtime safety)
/** @var int $attempts */
$attempts = Cache::get('login:attempts', 0);

// Option 2: Runtime check
$attempts = Cache::get('login:attempts', 0);
if (! is_int($attempts)) {
    return 0;
}

return $attempts;

Becomes:

return Cache::integer('login:attempts', 0);

The default value is returned when the key is missing. If the cached value exists but is an unexpected type, an InvalidArgumentException is thrown.

@timacdonald
Copy link
Member

I like this feature.

I feel like this might suffer from the same fundamental type problem as #56481, where differing driver behaviour could cause problems.

Hopefully the below demonstrates how Cache::integer('count') would throw an exception for Redis but work well with a database driver. I wonder if these differences in types returned need to be improved before we look at this one, otherwise it is only useable in certain scenarios.

Redis

<?php

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    Cache::store('redis')->put('count', 1);

    dump(
        Cache::store('redis')->get('count'),
    );

    Cache::store('redis')->increment('count');
    Cache::store('redis')->increment('count');

    dump(
        Cache::store('redis')->get('count'),
    );
});
Screenshot 2026-01-23 at 14 54 08

Database

Screenshot 2026-01-23 at 14 54 19
<?php

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    Cache::store('database')->put('count', 1);

    dump(
        Cache::store('database')->get('count'),
    );

    Cache::store('database')->increment('count');
    Cache::store('database')->increment('count');

    dump(
        Cache::store('database')->get('count'),
    );
});

@shaedrich
Copy link
Contributor

shaedrich commented Jan 23, 2026

It's a numeric string—couldn't this be cast?

On the other hand, we might adopt some Redis types for this feature:

  • LIST (to an extent SET) – array_is_list()
  • HASH!array_is_list()
  • JSON– could be used with jsonpath/dot notation

@ahinkle
Copy link
Contributor Author

ahinkle commented Jan 24, 2026

Valid point - thanks! Is integer() the only one affected here? Would casting be acceptable if the string is a valid integer?

Edit: applied this change for both integer and float.

@taylorotwell taylorotwell merged commit a16a88b into laravel:12.x Jan 30, 2026
70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants