Skip to content

Commit d6c86eb

Browse files
1edweaverryan
authored andcommitted
[Live] Control the type of generated URLs
1 parent a0fb279 commit d6c86eb

File tree

12 files changed

+121
-3
lines changed

12 files changed

+121
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- Allow multiple `LiveListener` attributes on a single method.
77
- Requests to LiveComponent are sent as POST by default
88
- Add method prop to AsLiveComponent to still allow GET requests, usage: `#[AsLiveComponent(method: 'get')]`
9+
- Add a new `urlReferenceType` parameter to `AsLiveComponent`, which allows to
10+
generate different type URL (e.g. absolute) for the component Ajax calls.
911

1012
## 2.13.2
1113

doc/index.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3279,6 +3279,26 @@ Then specify this new route on your component:
32793279
use DefaultActionTrait;
32803280
}
32813281
3282+
.. versionadded:: 2.14
3283+
3284+
The ``urlReferenceType`` option was added in LiveComponents 2.14.
3285+
3286+
You can also control the type of the generated URL:
3287+
3288+
.. code-block:: diff
3289+
3290+
// src/Components/RandomNumber.php
3291+
+ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
3292+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
3293+
use Symfony\UX\LiveComponent\DefaultActionTrait;
3294+
3295+
- #[AsLiveComponent]
3296+
+ #[AsLiveComponent(urlReferenceType: UrlGeneratorInterface::ABSOLUTE_URL)]
3297+
class RandomNumber
3298+
{
3299+
use DefaultActionTrait;
3300+
}
3301+
32823302
Add a Hook on LiveProp Update
32833303
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32843304

src/Attribute/AsLiveComponent.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\UX\LiveComponent\Attribute;
1313

14+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
1415
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
1516

1617
/**
@@ -33,6 +34,7 @@ final class AsLiveComponent extends AsTwigComponent
3334
* @param string $attributesVar The name of the special "attributes" variable in the template
3435
* @param bool $csrf Whether to enable CSRF protection (default: true)
3536
* @param string $route The route used to render the component & handle actions (default: ux_live_component)
37+
* @param int $urlReferenceType Which type of URL should be generated for the given route. Use the constants from UrlGeneratorInterface (default: absolute path, e.g. "/dir/file").
3638
*/
3739
public function __construct(
3840
string $name = null,
@@ -43,6 +45,7 @@ public function __construct(
4345
public bool $csrf = true,
4446
public string $route = 'ux_live_component',
4547
public string $method = 'post',
48+
public int $urlReferenceType = UrlGeneratorInterface::ABSOLUTE_PATH,
4649
) {
4750
parent::__construct($name, $template, $exposePublicProps, $attributesVar);
4851

@@ -64,6 +67,7 @@ public function serviceConfig(): array
6467
'csrf' => $this->csrf,
6568
'route' => $this->route,
6669
'method' => $this->method,
70+
'url_reference_type' => $this->urlReferenceType,
6771
]);
6872
}
6973

src/DependencyInjection/LiveComponentExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function load(array $configs, ContainerBuilder $container): void
8989
AsLiveComponent::class,
9090
function (ChildDefinition $definition, AsLiveComponent $attribute) {
9191
$definition
92-
->addTag('twig.component', array_filter($attribute->serviceConfig()))
92+
->addTag('twig.component', array_filter($attribute->serviceConfig(), static fn ($v) => null !== $v && '' !== $v))
9393
->addTag('controller.service_arguments')
9494
;
9595
}

src/Twig/LiveComponentRuntime.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ public function getComponentUrl(string $name, array $props = []): string
4545

4646
$metadata = $this->factory->metadataFor($mounted->getName());
4747

48-
return $this->urlGenerator->generate($metadata->get('route'), $params);
48+
return $this->urlGenerator->generate($metadata->get('route'), $params, $metadata->get('url_reference_type'));
4949
}
5050
}

src/Util/LiveControllerAttributesCreator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function attributesForRendering(MountedComponent $mounted, ComponentMetad
6161
$attributesCollection = $this->attributeHelper->create();
6262
$attributesCollection->setLiveController($mounted->getName());
6363

64-
$url = $this->urlGenerator->generate($metadata->get('route'), ['_live_component' => $mounted->getName()]);
64+
$url = $this->urlGenerator->generate($metadata->get('route'), ['_live_component' => $mounted->getName()], $metadata->get('url_reference_type'));
6565
$attributesCollection->setUrl($url);
6666

6767
$liveListeners = AsLiveComponent::liveListeners($mounted->getComponent());
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
4+
5+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
6+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
7+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
8+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
9+
use Symfony\UX\LiveComponent\DefaultActionTrait;
10+
11+
/**
12+
* @author Gábor Egyed <[email protected]>
13+
*/
14+
#[AsLiveComponent('with_absolute_url', urlReferenceType: UrlGeneratorInterface::ABSOLUTE_URL)]
15+
final class WithAbsoluteUrl
16+
{
17+
use DefaultActionTrait;
18+
19+
#[LiveProp(writable: true, url: true)]
20+
public int $count = 0;
21+
22+
#[LiveAction]
23+
public function increase(): void
24+
{
25+
++$this->count;
26+
}
27+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
{{ component_url('component1', {prop1: null, prop2: date}) }}
22
{{ component_url('alternate_route') }}
3+
{{ component_url('with_absolute_url') }}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div{{ attributes }}>
2+
From absolute url. Count: {{ count }}
3+
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ component('with_absolute_url') }}

tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\UX\LiveComponent\Tests\Functional\EventListener;
1313

1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
use Symfony\Component\DomCrawler\Crawler;
1516
use Symfony\UX\LiveComponent\Tests\LiveComponentTestHelper;
1617
use Zenstruck\Browser\Test\HasBrowser;
1718

@@ -154,4 +155,62 @@ public function testQueryStringMappingAttribute()
154155

155156
$this->assertEquals($expected, $queryMapping);
156157
}
158+
159+
public function testAbsoluteUrl(): void
160+
{
161+
$div = $this->browser()
162+
->visit('/render-template/render_with_absolute_url')
163+
->assertSuccessful()
164+
->assertContains('Count: 0')
165+
->crawler()
166+
->filter('div')
167+
;
168+
169+
$props = json_decode($div->attr('data-live-props-value'), true);
170+
171+
$this->assertSame('live', $div->attr('data-controller'));
172+
$this->assertSame('http://localhost/_components/with_absolute_url', $div->attr('data-live-url-value'));
173+
$this->assertNotNull($div->attr('data-live-csrf-value'));
174+
$this->assertCount(3, $props);
175+
$this->assertArrayHasKey('@checksum', $props);
176+
$this->assertArrayHasKey('@attributes', $props);
177+
$this->assertArrayHasKey('data-live-id', $props['@attributes']);
178+
$this->assertArrayHasKey('count', $props);
179+
$this->assertSame($props['count'], 0);
180+
}
181+
182+
public function testAbsoluteUrlWithLiveQueryProp()
183+
{
184+
$token = null;
185+
$props = [];
186+
$div = $this->browser()
187+
->get('/render-template/render_with_absolute_url?count=1')
188+
->assertSuccessful()
189+
->assertContains('Count: 1')
190+
->use(function (Crawler $crawler) use (&$token, &$props) {
191+
$div = $crawler->filter('div')->first();
192+
$token = $div->attr('data-live-csrf-value');
193+
$props = json_decode($div->attr('data-live-props-value'), true);
194+
})
195+
->post('http://localhost/_components/with_absolute_url/increase', [
196+
'headers' => ['X-CSRF-TOKEN' => $token],
197+
'body' => ['data' => json_encode(['props' => $props])],
198+
])
199+
->assertContains('Count: 2')
200+
->crawler()
201+
->filter('div')
202+
;
203+
204+
$props = json_decode($div->attr('data-live-props-value'), true);
205+
206+
$this->assertSame('live', $div->attr('data-controller'));
207+
$this->assertSame('http://localhost/_components/with_absolute_url', $div->attr('data-live-url-value'));
208+
$this->assertNotNull($div->attr('data-live-csrf-value'));
209+
$this->assertCount(3, $props);
210+
$this->assertArrayHasKey('@checksum', $props);
211+
$this->assertArrayHasKey('@attributes', $props);
212+
$this->assertArrayHasKey('data-live-id', $props['@attributes']);
213+
$this->assertArrayHasKey('count', $props);
214+
$this->assertSame($props['count'], 2);
215+
}
157216
}

tests/Integration/Twig/LiveComponentExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ public function testGetComponentUrl(): void
2626

2727
$this->assertStringContainsString('/_components/component1?props=%7B%22prop1%22:null,%22prop2%22:%222022-10-06T00:00:00%2B00:00%22,%22prop3%22:null,', $rendered);
2828
$this->assertStringContainsString('/alt/alternate_route?', $rendered);
29+
$this->assertStringContainsString('http://localhost/_components/with_absolute_url?', $rendered);
2930
}
3031
}

0 commit comments

Comments
 (0)