From dc08ee68751c6f20d09c96718b26da0456603579 Mon Sep 17 00:00:00 2001 From: Moritz Mazetti Date: Mon, 26 Oct 2026 08:51:42 +0100 Subject: [PATCH] Fix: Bound route keys that are integers crashed tag resolution A tag binding to a model with an integer route key, like user:{user}, passed an int to str_replace() under strict_types, so every request to the route failed with a TypeError. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GGNGyJLoFTM5C474gZPW5q --- src/ResponseCache.php | 2 +- tests/Feature/TagBindingTest.php | 23 +++++++++++++++++++++ tests/TestCase.php | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/TagBindingTest.php diff --git a/src/ResponseCache.php b/src/ResponseCache.php index 65403ed..75e53e9 100644 --- a/src/ResponseCache.php +++ b/src/ResponseCache.php @@ -323,7 +323,7 @@ protected function replaceBinding(string $tag, Request $request): string // Replace the matched expression (`{foo}`) with the value found in // the request inside the tag - $tag = str_replace($match, $value, $tag); + $tag = str_replace($match, (string) $value, $tag); } return $this->resolvedTags[$tag] = $tag; diff --git a/tests/Feature/TagBindingTest.php b/tests/Feature/TagBindingTest.php new file mode 100644 index 0000000..49fdfbf --- /dev/null +++ b/tests/Feature/TagBindingTest.php @@ -0,0 +1,23 @@ +app['cache']->flush(); +}); + +it('resolves bound models with integer keys in tags', function () { + test()->get('/models/1')->assertContent('1'); + test()->get('/models/2')->assertContent('2'); + + ResponseCache::flush(['models.2']); + + test()->get('/models/1')->assertContent('1'); + test()->get('/models/2')->assertContent('3'); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index d614fb5..f5305eb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,8 @@ namespace Matchory\ResponseCache\Tests; +use Illuminate\Contracts\Routing\UrlRoutable; +use Illuminate\Routing\Middleware\SubstituteBindings; use InvalidArgumentException; use Matchory\ResponseCache\Http\Middleware\BypassCache; use Matchory\ResponseCache\Http\Middleware\CacheResponse; @@ -39,6 +41,39 @@ protected function defineRoutes($router): void ->middleware(CacheResponse::class) ->middleware(BypassCache::class) ->name('bypass'); + + // Binds like an Eloquent model with an integer key + $router->bind('model', fn(string $value) => new class ((int) $value) implements UrlRoutable { + public function __construct(private readonly int $key) {} + + public function getRouteKey(): int + { + return $this->key; + } + + public function getRouteKeyName(): string + { + return 'id'; + } + + public function resolveRouteBinding($value, $field = null): ?self + { + return null; + } + + public function resolveChildRouteBinding($childType, $value, $field): ?self + { + return null; + } + }); + $router + ->get('/models/{model}', function () { + static $i = 0; + + return ++$i; + }) + ->middleware([SubstituteBindings::class, CacheResponse::class . ':models.{model}']) + ->name('models'); } protected function getPackageProviders($app): array