From 4fc3fc2ef52b7e63e098b8bda82e8fc47271b348 Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Tue, 25 Nov 2025 15:08:47 +0000 Subject: [PATCH 01/15] Add cascade attribute for cascade data autoloading --- src/Attributes/Cascade.php | 41 ++++++ src/Hooks/CascadeVariablesAutoloader.php | 42 ++++++ src/ServiceProvider.php | 7 + .../CascadeVariablesAutoloaderTest.php | 135 ++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 src/Attributes/Cascade.php create mode 100644 src/Hooks/CascadeVariablesAutoloader.php create mode 100644 tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php diff --git a/src/Attributes/Cascade.php b/src/Attributes/Cascade.php new file mode 100644 index 0000000..cdad140 --- /dev/null +++ b/src/Attributes/Cascade.php @@ -0,0 +1,41 @@ +toArray(); + } + + if (! isset($this->keys)) { + return $data; + } + + return collect($this->keys) + ->mapWithKeys(function ($default, $key) use ($data) { + if (is_numeric($key)) { + $key = $default; + $default = null; + if (! array_key_exists($key, $data)) { + throw new CascadeDataNotFoundException($key); + } + } + + return [$key => Arr::get($data, $key, $default)]; + }) + ->all(); + } +} diff --git a/src/Hooks/CascadeVariablesAutoloader.php b/src/Hooks/CascadeVariablesAutoloader.php new file mode 100644 index 0000000..2adb73b --- /dev/null +++ b/src/Hooks/CascadeVariablesAutoloader.php @@ -0,0 +1,42 @@ +isUsingAntlers($view)) { + return; + } + + $attribute = $component + ->getAttributes() + ->whereInstanceOf(CascadeAttribute::class) + ->first(); + if (! $attribute) { + return; + } + + $cascade = $attribute->getCascadeData(); + + $view->with(array_merge($cascade, $data)); + } + + protected function isUsingAntlers(View $view): bool + { + return $view->getEngine() instanceof AntlersEngine; + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 792e1ba..1488afe 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Routing\Router; use Livewire\Livewire; use Livewire\Mechanisms\HandleComponents\Synthesizers\Synth; +use MarcoRieser\Livewire\Hooks\CascadeVariablesAutoloader; use MarcoRieser\Livewire\Hooks\ComputedPropertiesAutoloader; use MarcoRieser\Livewire\Hooks\SynthesizerAugmentor; use MarcoRieser\Livewire\Http\Middleware\ResolveCurrentSiteByLivewireUrl; @@ -24,6 +25,7 @@ public function register(): void $this->registerSynthesizerAugmentation(); $this->registerComputedPropertiesAutoloader(); + $this->registerCascadeVariablesAutoloader(); } public function bootAddon(): void @@ -75,4 +77,9 @@ protected function registerComputedPropertiesAutoloader(): void { Livewire::componentHook(ComputedPropertiesAutoloader::class); } + + protected function registerCascadeVariablesAutoloader(): void + { + Livewire::componentHook(CascadeVariablesAutoloader::class); + } } diff --git a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php new file mode 100644 index 0000000..cf060bb --- /dev/null +++ b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php @@ -0,0 +1,135 @@ +getAntlersLivewireComponent(); + + $testable = Livewire::test($component); + + $testable->assertViewHas('homepage', '/'); + $testable->assertViewHas('environment', 'testing'); + } + + #[Test] + public function cascade_variables_are_not_autoloaded_in_blade() + { + $component = $this->getBladeLivewireComponent(); + + $testable = Livewire::test($component); + + $testable = $testable->assertViewMissing('homepage'); + $testable = $testable->assertViewMissing('environment'); + } + + #[Test] + public function cascade_variables_are_autoloaded_selectively() + { + $component = $this->getSelectedLivewireComponent(); + + $testable = Livewire::test($component); + + $testable = $testable->assertViewHas('homepage', '/'); + $testable = $testable->assertViewHas('my_global', true); + $testable = $testable->assertViewMissing('environment'); + } + + #[Test] + public function cascade_variables_throw_exception_when_invalid() + { + $this->expectException(ViewException::class); + $this->expectExceptionMessage('Cascade data [my_invalid] not found'); + + $component = $this->getInvalidLivewireComponent(); + + $testable = Livewire::test($component); + } + + #[Test] + public function cascade_variables_are_not_autoloaded_when_attribute_excluded() + { + $component = $this->getExcludedLivewireComponent(); + + $testable = Livewire::test($component); + + $testable = $testable->assertViewMissing('homepage'); + $testable = $testable->assertViewMissing('environment'); + } + + protected function getAntlersLivewireComponent(): Component + { + return new + #[Cascade] + class extends Component + { + public function render() + { + return view('antlers'); + } + }; + } + + protected function getBladeLivewireComponent(): Component + { + return new + #[Cascade] + class extends Component + { + public function render() + { + return view('blade'); + } + }; + } + + protected function getSelectedLivewireComponent(): Component + { + return new + #[Cascade(['homepage', 'my_global' => true])] + class extends Component + { + public function render() + { + return view('antlers'); + } + }; + } + + protected function getInvalidLivewireComponent(): Component + { + return new + #[Cascade(['my_invalid'])] + class extends Component + { + public function render() + { + return view('antlers'); + } + }; + } + + protected function getExcludedLivewireComponent(): Component + { + return new class extends Component + { + public function render() + { + return view('antlers'); + } + }; + } +} From 2ed9e90cdf6f0a433680b7454083552f0cef5cd4 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Mon, 15 Dec 2025 15:38:58 +0100 Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=92=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Hooks/CascadeVariablesAutoloader.php | 1 + .../CascadeVariablesAutoloaderTest.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Hooks/CascadeVariablesAutoloader.php b/src/Hooks/CascadeVariablesAutoloader.php index 2adb73b..9cd44d3 100644 --- a/src/Hooks/CascadeVariablesAutoloader.php +++ b/src/Hooks/CascadeVariablesAutoloader.php @@ -26,6 +26,7 @@ public function render($view, $data): void ->getAttributes() ->whereInstanceOf(CascadeAttribute::class) ->first(); + if (! $attribute) { return; } diff --git a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php index cf060bb..b260d24 100644 --- a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php +++ b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php @@ -32,8 +32,8 @@ public function cascade_variables_are_not_autoloaded_in_blade() $testable = Livewire::test($component); - $testable = $testable->assertViewMissing('homepage'); - $testable = $testable->assertViewMissing('environment'); + $testable->assertViewMissing('homepage'); + $testable->assertViewMissing('environment'); } #[Test] @@ -43,9 +43,9 @@ public function cascade_variables_are_autoloaded_selectively() $testable = Livewire::test($component); - $testable = $testable->assertViewHas('homepage', '/'); - $testable = $testable->assertViewHas('my_global', true); - $testable = $testable->assertViewMissing('environment'); + $testable->assertViewHas('homepage', '/'); + $testable->assertViewHas('my_global', true); + $testable->assertViewMissing('environment'); } #[Test] @@ -56,7 +56,7 @@ public function cascade_variables_throw_exception_when_invalid() $component = $this->getInvalidLivewireComponent(); - $testable = Livewire::test($component); + Livewire::test($component); } #[Test] @@ -66,8 +66,8 @@ public function cascade_variables_are_not_autoloaded_when_attribute_excluded() $testable = Livewire::test($component); - $testable = $testable->assertViewMissing('homepage'); - $testable = $testable->assertViewMissing('environment'); + $testable->assertViewMissing('homepage'); + $testable->assertViewMissing('environment'); } protected function getAntlersLivewireComponent(): Component From c34f53b51463f3117f48ddef674839afe617d52a Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Mon, 15 Dec 2025 16:22:18 +0100 Subject: [PATCH 03/15] Use current site for cascade hydration --- src/Attributes/Cascade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Attributes/Cascade.php b/src/Attributes/Cascade.php index cdad140..380a57b 100644 --- a/src/Attributes/Cascade.php +++ b/src/Attributes/Cascade.php @@ -5,6 +5,7 @@ use Livewire\Features\SupportAttributes\Attribute as LivewireAttribute; use Statamic\Exceptions\CascadeDataNotFoundException; use Statamic\Facades\Cascade as CascadeFacade; +use Statamic\Facades\Site; use Statamic\Support\Arr; #[\Attribute] @@ -17,6 +18,8 @@ public function __construct( public function getCascadeData(): array { if (! $data = CascadeFacade::toArray()) { + CascadeFacade::withSite(Site::current()); + $data = CascadeFacade::hydrate()->toArray(); } From 381d2917bc0dbb050637836b76bc65ffa3cb3747 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Mon, 15 Dec 2025 16:49:08 +0100 Subject: [PATCH 04/15] Hydrate based on original Livewire URL and method --- src/Attributes/Cascade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Attributes/Cascade.php b/src/Attributes/Cascade.php index 380a57b..15386c7 100644 --- a/src/Attributes/Cascade.php +++ b/src/Attributes/Cascade.php @@ -2,7 +2,9 @@ namespace MarcoRieser\Livewire\Attributes; +use Illuminate\Support\Facades\Request; use Livewire\Features\SupportAttributes\Attribute as LivewireAttribute; +use Livewire\Livewire; use Statamic\Exceptions\CascadeDataNotFoundException; use Statamic\Facades\Cascade as CascadeFacade; use Statamic\Facades\Site; @@ -19,6 +21,7 @@ public function getCascadeData(): array { if (! $data = CascadeFacade::toArray()) { CascadeFacade::withSite(Site::current()); + CascadeFacade::withRequest(Request::create(uri: Livewire::originalUrl(), method: Livewire::originalMethod())); $data = CascadeFacade::hydrate()->toArray(); } From c00cd16c4946df07bdf95f9bd8f316ba0fec885f Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Mon, 15 Dec 2025 17:16:50 +0100 Subject: [PATCH 05/15] Extract methods --- src/Attributes/Cascade.php | 40 ++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Attributes/Cascade.php b/src/Attributes/Cascade.php index 15386c7..a8a4152 100644 --- a/src/Attributes/Cascade.php +++ b/src/Attributes/Cascade.php @@ -3,10 +3,12 @@ namespace MarcoRieser\Livewire\Attributes; use Illuminate\Support\Facades\Request; +use Illuminate\Support\Str; use Livewire\Features\SupportAttributes\Attribute as LivewireAttribute; use Livewire\Livewire; use Statamic\Exceptions\CascadeDataNotFoundException; use Statamic\Facades\Cascade as CascadeFacade; +use Statamic\Facades\Entry; use Statamic\Facades\Site; use Statamic\Support\Arr; @@ -14,19 +16,21 @@ class Cascade extends LivewireAttribute { public function __construct( - public $keys = null, + public array $keys = [], + public bool $content = true, ) {} public function getCascadeData(): array { if (! $data = CascadeFacade::toArray()) { - CascadeFacade::withSite(Site::current()); - CascadeFacade::withRequest(Request::create(uri: Livewire::originalUrl(), method: Livewire::originalMethod())); + $this->hydrateSite(); + $this->hydrateRequest(); + $this->hydrateContent(); $data = CascadeFacade::hydrate()->toArray(); } - if (! isset($this->keys)) { + if (! $this->keys) { return $data; } @@ -35,6 +39,7 @@ public function getCascadeData(): array if (is_numeric($key)) { $key = $default; $default = null; + if (! array_key_exists($key, $data)) { throw new CascadeDataNotFoundException($key); } @@ -44,4 +49,31 @@ public function getCascadeData(): array }) ->all(); } + + protected function hydrateSite(): void + { + CascadeFacade::withSite(Site::current()); + } + + protected function hydrateRequest(): void + { + CascadeFacade::withRequest(Request::create(uri: Livewire::originalUrl(), method: Livewire::originalMethod())); + } + + protected function hydrateContent(): void + { + if (! $this->content) { + return; + } + + $url = Str::of(Livewire::originalUrl()) + ->after(Site::current()->absoluteUrl()) + ->start('/'); + + if (! ($entry = Entry::findByUri($url))) { + return; + } + + CascadeFacade::withContent($entry); + } } From baedbdfe8d91858ca9ed526f65914be433c254e1 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Mon, 15 Dec 2025 17:27:53 +0100 Subject: [PATCH 06/15] Restore content of the current site --- src/Attributes/Cascade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Attributes/Cascade.php b/src/Attributes/Cascade.php index a8a4152..f8134b3 100644 --- a/src/Attributes/Cascade.php +++ b/src/Attributes/Cascade.php @@ -70,7 +70,7 @@ protected function hydrateContent(): void ->after(Site::current()->absoluteUrl()) ->start('/'); - if (! ($entry = Entry::findByUri($url))) { + if (! ($entry = Entry::findByUri($url, Site::current()))) { return; } From 8cd99003d89e29218419596a51534a552d5fd3d6 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Tue, 13 Jan 2026 17:09:46 +0100 Subject: [PATCH 07/15] Type hint variable --- src/Hooks/CascadeVariablesAutoloader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Hooks/CascadeVariablesAutoloader.php b/src/Hooks/CascadeVariablesAutoloader.php index 9cd44d3..5ca3a9b 100644 --- a/src/Hooks/CascadeVariablesAutoloader.php +++ b/src/Hooks/CascadeVariablesAutoloader.php @@ -22,6 +22,7 @@ public function render($view, $data): void return; } + /** @var ?CascadeAttribute $attribute */ $attribute = $component ->getAttributes() ->whereInstanceOf(CascadeAttribute::class) From 7796c512b32731fb543bb34b065cbe8e4fa307ff Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Tue, 13 Jan 2026 17:15:23 +0100 Subject: [PATCH 08/15] Extract the hydration by Livewire Url to a middleware, so Blade benefits from it as well --- src/Attributes/Cascade.php | 43 +-------------- .../HydrateCascadeByLivewireUrl.php | 53 +++++++++++++++++++ src/ServiceProvider.php | 2 + 3 files changed, 57 insertions(+), 41 deletions(-) create mode 100644 src/Http/Middleware/HydrateCascadeByLivewireUrl.php diff --git a/src/Attributes/Cascade.php b/src/Attributes/Cascade.php index f8134b3..7e2b0ea 100644 --- a/src/Attributes/Cascade.php +++ b/src/Attributes/Cascade.php @@ -2,31 +2,19 @@ namespace MarcoRieser\Livewire\Attributes; -use Illuminate\Support\Facades\Request; -use Illuminate\Support\Str; +use Illuminate\Support\Arr; use Livewire\Features\SupportAttributes\Attribute as LivewireAttribute; -use Livewire\Livewire; use Statamic\Exceptions\CascadeDataNotFoundException; use Statamic\Facades\Cascade as CascadeFacade; -use Statamic\Facades\Entry; -use Statamic\Facades\Site; -use Statamic\Support\Arr; #[\Attribute] class Cascade extends LivewireAttribute { - public function __construct( - public array $keys = [], - public bool $content = true, - ) {} + public function __construct(public array $keys = []) {} public function getCascadeData(): array { if (! $data = CascadeFacade::toArray()) { - $this->hydrateSite(); - $this->hydrateRequest(); - $this->hydrateContent(); - $data = CascadeFacade::hydrate()->toArray(); } @@ -49,31 +37,4 @@ public function getCascadeData(): array }) ->all(); } - - protected function hydrateSite(): void - { - CascadeFacade::withSite(Site::current()); - } - - protected function hydrateRequest(): void - { - CascadeFacade::withRequest(Request::create(uri: Livewire::originalUrl(), method: Livewire::originalMethod())); - } - - protected function hydrateContent(): void - { - if (! $this->content) { - return; - } - - $url = Str::of(Livewire::originalUrl()) - ->after(Site::current()->absoluteUrl()) - ->start('/'); - - if (! ($entry = Entry::findByUri($url, Site::current()))) { - return; - } - - CascadeFacade::withContent($entry); - } } diff --git a/src/Http/Middleware/HydrateCascadeByLivewireUrl.php b/src/Http/Middleware/HydrateCascadeByLivewireUrl.php new file mode 100644 index 0000000..ca31a7c --- /dev/null +++ b/src/Http/Middleware/HydrateCascadeByLivewireUrl.php @@ -0,0 +1,53 @@ +hydrateSite(); + $this->hydrateRequest(); + $this->hydrateContent(); + + return $next($request); + } + + protected function hydrateSite(): void + { + Cascade::withSite(Site::current()); + } + + protected function hydrateRequest(): void + { + Cascade::withRequest(RequestFacade::create(uri: Livewire::originalUrl(), method: Livewire::originalMethod())); + } + + protected function hydrateContent(): void + { + $url = Str::of(Livewire::originalUrl()) + ->after(Site::current()->absoluteUrl()) + ->start('/'); + + if (! ($entry = Entry::findByUri($url, Site::current()))) { + return; + } + + Cascade::withContent($entry); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 1488afe..dcef510 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -9,6 +9,7 @@ use MarcoRieser\Livewire\Hooks\CascadeVariablesAutoloader; use MarcoRieser\Livewire\Hooks\ComputedPropertiesAutoloader; use MarcoRieser\Livewire\Hooks\SynthesizerAugmentor; +use MarcoRieser\Livewire\Http\Middleware\HydrateCascadeByLivewireUrl; use MarcoRieser\Livewire\Http\Middleware\ResolveCurrentSiteByLivewireUrl; use Statamic\Http\Middleware\Localize; use Statamic\Providers\AddonServiceProvider; @@ -46,6 +47,7 @@ protected function bootLocalization(): void ->each(fn (Route $route) => $route->middleware([ ResolveCurrentSiteByLivewireUrl::class, Localize::class, + HydrateCascadeByLivewireUrl::class, ])); } From 839b285d4814bd541ed9c09e6b892ade29080610 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Thu, 15 Jan 2026 11:10:53 +0100 Subject: [PATCH 09/15] wip --- HydrateCascadeByLivewireUrlTet.php | 73 +++++++++++++++++++ .../CascadeVariablesAutoloaderTest.php | 9 +++ 2 files changed, 82 insertions(+) create mode 100644 HydrateCascadeByLivewireUrlTet.php diff --git a/HydrateCascadeByLivewireUrlTet.php b/HydrateCascadeByLivewireUrlTet.php new file mode 100644 index 0000000..5e4cffe --- /dev/null +++ b/HydrateCascadeByLivewireUrlTet.php @@ -0,0 +1,73 @@ +runMiddleware('http://example.com/about'); + + $this->assertSame($entry->id(), Cascade::content()->id()); + + Cascade::hydrate(); + + $this->assertSame('http://example.com/about', Cascade::get('current_url')); + $this->assertSame($entry->id(), Cascade::get('page')->id()); + } + + #[Test] + public function cascade_does_not_set_content_when_no_entry_matches() + { + $this->runMiddleware('http://example.com/missing'); + + $this->assertNull(Cascade::content()); + + Cascade::hydrate(); + + $this->assertSame('http://example.com/missing', Cascade::get('current_url')); + } + + protected function setUp(): void + { + parent::setUp(); + + Site::setSiteValue('default', 'url', 'http://example.com'); + Site::setCurrent('default'); + + Collection::make('pages')->routes('/{slug}')->save(); + + Entry::make() + ->collection('pages') + ->slug('about') + ->locale('default') + ->data(['title' => 'About']) + ->save(); + } + + protected function runMiddleware(string $originalUrl): void + { + Livewire::shouldReceive('originalUrl')->andReturn($originalUrl); + Livewire::shouldReceive('originalMethod')->andReturn('GET'); + + $request = Request::create('http://example.com/livewire/update', 'POST'); + $this->app->instance('request', $request); + + (new HydrateCascadeByLivewireUrl)->handle($request, fn () => new Response); + } +} diff --git a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php index b260d24..e4c0965 100644 --- a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php +++ b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php @@ -70,6 +70,15 @@ public function cascade_variables_are_not_autoloaded_when_attribute_excluded() $testable->assertViewMissing('environment'); } + #[Test] + public function marco() + { + $component = $this->getAntlersLivewireComponent(); + + $testable = Livewire::test($component); + + } + protected function getAntlersLivewireComponent(): Component { return new From 5e598375c06ecfd5bf40b6bad3e8878feb7f3f51 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Fri, 16 Jan 2026 06:07:00 +0100 Subject: [PATCH 10/15] simplify --- src/Http/Middleware/HydrateCascadeByLivewireUrl.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Http/Middleware/HydrateCascadeByLivewireUrl.php b/src/Http/Middleware/HydrateCascadeByLivewireUrl.php index ca31a7c..7d8c09b 100644 --- a/src/Http/Middleware/HydrateCascadeByLivewireUrl.php +++ b/src/Http/Middleware/HydrateCascadeByLivewireUrl.php @@ -5,10 +5,9 @@ use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Request as RequestFacade; -use Illuminate\Support\Str; use Livewire\Livewire; use Statamic\Facades\Cascade; -use Statamic\Facades\Entry; +use Statamic\Facades\Data; use Statamic\Facades\Site; use Symfony\Component\HttpFoundation\Response; @@ -40,14 +39,6 @@ protected function hydrateRequest(): void protected function hydrateContent(): void { - $url = Str::of(Livewire::originalUrl()) - ->after(Site::current()->absoluteUrl()) - ->start('/'); - - if (! ($entry = Entry::findByUri($url, Site::current()))) { - return; - } - - Cascade::withContent($entry); + Cascade::withContent(Data::findByRequestUrl(Livewire::originalUrl())); } } From 0bf0a1f1ddf503c56e0cd89fe6f6f5b9a59a10cc Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Fri, 16 Jan 2026 07:55:56 +0100 Subject: [PATCH 11/15] remove unnecessary file --- HydrateCascadeByLivewireUrlTet.php | 73 ------------------------------ 1 file changed, 73 deletions(-) delete mode 100644 HydrateCascadeByLivewireUrlTet.php diff --git a/HydrateCascadeByLivewireUrlTet.php b/HydrateCascadeByLivewireUrlTet.php deleted file mode 100644 index 5e4cffe..0000000 --- a/HydrateCascadeByLivewireUrlTet.php +++ /dev/null @@ -1,73 +0,0 @@ -runMiddleware('http://example.com/about'); - - $this->assertSame($entry->id(), Cascade::content()->id()); - - Cascade::hydrate(); - - $this->assertSame('http://example.com/about', Cascade::get('current_url')); - $this->assertSame($entry->id(), Cascade::get('page')->id()); - } - - #[Test] - public function cascade_does_not_set_content_when_no_entry_matches() - { - $this->runMiddleware('http://example.com/missing'); - - $this->assertNull(Cascade::content()); - - Cascade::hydrate(); - - $this->assertSame('http://example.com/missing', Cascade::get('current_url')); - } - - protected function setUp(): void - { - parent::setUp(); - - Site::setSiteValue('default', 'url', 'http://example.com'); - Site::setCurrent('default'); - - Collection::make('pages')->routes('/{slug}')->save(); - - Entry::make() - ->collection('pages') - ->slug('about') - ->locale('default') - ->data(['title' => 'About']) - ->save(); - } - - protected function runMiddleware(string $originalUrl): void - { - Livewire::shouldReceive('originalUrl')->andReturn($originalUrl); - Livewire::shouldReceive('originalMethod')->andReturn('GET'); - - $request = Request::create('http://example.com/livewire/update', 'POST'); - $this->app->instance('request', $request); - - (new HydrateCascadeByLivewireUrl)->handle($request, fn () => new Response); - } -} From c533981aa3d227bde52358b7a5c8c5495a117011 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Fri, 16 Jan 2026 08:12:08 +0100 Subject: [PATCH 12/15] restructure service provider --- src/ServiceProvider.php | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index dcef510..097d638 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -16,6 +16,8 @@ class ServiceProvider extends AddonServiceProvider { + protected array $middlewares = []; + protected $tags = [ 'MarcoRieser\Livewire\Tags\Livewire', ]; @@ -32,8 +34,10 @@ public function register(): void public function bootAddon(): void { $this->bootLocalization(); + $this->bootCascadeRestoration(); $this->bootReplacers(); $this->bootSynthesizers(); + $this->bootMiddlewares(); } protected function bootLocalization(): void @@ -42,13 +46,13 @@ protected function bootLocalization(): void return; } - collect($this->app->make(Router::class)->getRoutes()->getRoutes()) - ->filter(fn (Route $route) => $route->named('*livewire.update')) - ->each(fn (Route $route) => $route->middleware([ - ResolveCurrentSiteByLivewireUrl::class, - Localize::class, - HydrateCascadeByLivewireUrl::class, - ])); + $this->middlewares[] = ResolveCurrentSiteByLivewireUrl::class; + $this->middlewares[] = Localize::class; + } + + protected function bootCascadeRestoration(): void + { + $this->middlewares[] = HydrateCascadeByLivewireUrl::class; } protected function bootReplacers(): void @@ -84,4 +88,11 @@ protected function registerCascadeVariablesAutoloader(): void { Livewire::componentHook(CascadeVariablesAutoloader::class); } + + protected function bootMiddlewares(): void + { + collect($this->app->make(Router::class)->getRoutes()->getRoutes()) + ->filter(fn (Route $route) => $route->named('*livewire.update')) + ->each(fn (Route $route) => $route->middleware($this->middlewares)); + } } From 6cb9e0a78ed0f7a6b5e4c6901c6400ea21f0c898 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Fri, 16 Jan 2026 08:12:14 +0100 Subject: [PATCH 13/15] fix tests --- .../CascadeVariables/CascadeVariablesAutoloaderTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php index e4c0965..b260d24 100644 --- a/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php +++ b/tests/Features/CascadeVariables/CascadeVariablesAutoloaderTest.php @@ -70,15 +70,6 @@ public function cascade_variables_are_not_autoloaded_when_attribute_excluded() $testable->assertViewMissing('environment'); } - #[Test] - public function marco() - { - $component = $this->getAntlersLivewireComponent(); - - $testable = Livewire::test($component); - - } - protected function getAntlersLivewireComponent(): Component { return new From fc4bb7d737e1c3af27e5ffa110d3c802fd4e4489 Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Fri, 16 Jan 2026 11:53:44 +0100 Subject: [PATCH 14/15] add docs --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 406c044..66ee42e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ A third-party [Laravel Livewire](https://laravel-livewire.com/) integration for + [Static caching](#static-caching) + [`@script` and `@assets`](#--script--and---assets-) + [Computed Properties](#computed-properties) + + [Cascade](#cascade) + [Multi-Site / Localization](#multi-site---localization) + [Lazy Components](#lazy-components) + [Paginating Data](#paginating-data) @@ -192,6 +193,40 @@ public function entries() { {{ /entries }} ``` +### Cascade +Normally all the variables in the Cascade are only available on initial render and get lost between Livewire requests. This means you'd need pass in the required ones into the component yourself.
+To make our lives a bit easier, you can add the `#[Cascade]` attribute to your component. This is only needed for Antlers views and mirrors the logic of Blade's [`@cascade`](https://statamic.dev/blade#cascade-directive) directive. + +```php +use Livewire\Component; +use MarcoRieser\Livewire\Attributes\Cascade; + +#[Cascade] +class ShowArticle extends Component +{ + ... +} +``` + +Now you can access the variables from the Cascade directly in your Antlers view, even on subsequent renders: + +```antlers +

{{ title }}

+{{ seo_title }} +``` + +You can also limit which cascade keys are exposed (and provide defaults): + +```php +#[Cascade([ + 'title', + 'seo_title' => 'Fallback title', +])] +class ShowArticle extends Component {} +``` + +For subsequent requests, the addon restores the Cascade using the original Livewire URL, so site, request, and content data resolve as expected. + ### Multi-Site / Localization By default, your current site is persisted between Livewire requests automatically. In case you want to implement your own logic, you can disable `localization` in your published `config/statamic-livewire.php` config. From 86a67feeb48613284d0399482dcb3f6912c4283d Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Fri, 16 Jan 2026 11:55:07 +0100 Subject: [PATCH 15/15] add docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66ee42e..0a226c7 100644 --- a/README.md +++ b/README.md @@ -194,8 +194,8 @@ public function entries() { ``` ### Cascade -Normally all the variables in the Cascade are only available on initial render and get lost between Livewire requests. This means you'd need pass in the required ones into the component yourself.
-To make our lives a bit easier, you can add the `#[Cascade]` attribute to your component. This is only needed for Antlers views and mirrors the logic of Blade's [`@cascade`](https://statamic.dev/blade#cascade-directive) directive. +Normally all the variables in the Cascade are only available on initial render and get lost between Livewire requests. This means you'd need pass in the required ones into the component yourself. +To make our lives a bit easier, you can add the `#[Cascade]` attribute to your component.
This is only needed for Antlers views and mirrors the logic of Blade's [`@cascade`](https://statamic.dev/blade#cascade-directive) directive. ```php use Livewire\Component;