Skip to content

Commit 86040e0

Browse files
committed
Add alias option to url query mapping
1 parent 26df3f4 commit 86040e0

File tree

12 files changed

+77
-24
lines changed

12 files changed

+77
-24
lines changed

src/LiveComponent/assets/test/controller/query-binding.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ describe('LiveController query string binding', () => {
144144
expectCurrentSearch().toEqual('?prop=');
145145
});
146146

147-
148147
it('updates the URL with props changed by the server', async () => {
149148
const test = await createTest({ prop: ''}, (data: any) => `
150149
<div ${initComponent(data, {queryMapping: {prop: {name: 'prop'}}})}>
@@ -165,4 +164,26 @@ describe('LiveController query string binding', () => {
165164

166165
expectCurrentSearch().toEqual('?prop=foo');
167166
});
168-
})
167+
168+
it('uses custom name instead of prop name in the URL', async () => {
169+
const test = await createTest({ prop1: ''}, (data: any) => `
170+
<div ${initComponent(data, { queryMapping: {prop1: {name: 'alias1'} }})}></div>
171+
`)
172+
173+
// Set value
174+
test.expectsAjaxCall()
175+
.expectUpdatedData({prop1: 'foo'});
176+
177+
await test.component.set('prop1', 'foo', true);
178+
179+
expectCurrentSearch().toEqual('?alias1=foo');
180+
181+
// Remove value
182+
test.expectsAjaxCall()
183+
.expectUpdatedData({prop1: ''});
184+
185+
await test.component.set('prop1', '', true);
186+
187+
expectCurrentSearch().toEqual('?alias1=');
188+
});
189+
})

src/LiveComponent/src/Attribute/LiveProp.php

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

1212
namespace Symfony\UX\LiveComponent\Attribute;
1313

14+
use Symfony\UX\LiveComponent\Metadata\QueryMapping;
15+
1416
/**
1517
* An attribute to mark a property as a "LiveProp".
1618
*
@@ -99,14 +101,19 @@ public function __construct(
99101
private null|string|array $onUpdated = null,
100102

101103
/**
102-
* If true, this property will be synchronized with a query parameter
103-
* in the URL.
104+
* Whether to synchronize this property with a query parameter
105+
* in the URL. Pass true to configure the mapping automatically, or a
106+
* {@see QueryMapping} instance to configure the mapping.
104107
*/
105-
private bool $url = false,
108+
private bool|QueryMapping $url = false,
106109
) {
107110
if ($this->useSerializerForHydration && ($this->hydrateWith || $this->dehydrateWith)) {
108111
throw new \InvalidArgumentException('Cannot use useSerializerForHydration with hydrateWith or dehydrateWith.');
109112
}
113+
114+
if (true === $url) {
115+
$this->url = new QueryMapping();
116+
}
110117
}
111118

112119
/**
@@ -198,7 +205,7 @@ public function onUpdated(): null|string|array
198205
return $this->onUpdated;
199206
}
200207

201-
public function url(): bool
208+
public function url(): bool|QueryMapping
202209
{
203210
return $this->url;
204211
}

src/LiveComponent/src/Metadata/LiveComponentMetadataFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public function createLivePropMetadata(string $className, string $propertyName,
110110
$isTypeBuiltIn,
111111
$isTypeNullable,
112112
$collectionValueType,
113-
$liveProp->url()
113+
$liveProp->url() ?: null
114114
);
115115
}
116116

src/LiveComponent/src/Metadata/LivePropMetadata.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function __construct(
3030
private bool $isBuiltIn,
3131
private bool $allowsNull,
3232
private ?Type $collectionValueType,
33-
private bool $queryStringMapping,
33+
private ?QueryMapping $queryStringMapping,
3434
) {
3535
}
3636

@@ -54,7 +54,7 @@ public function allowsNull(): bool
5454
return $this->allowsNull;
5555
}
5656

57-
public function queryStringMapping(): bool
57+
public function queryStringMapping(): ?QueryMapping
5858
{
5959
return $this->queryStringMapping;
6060
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Metadata;
4+
5+
class QueryMapping
6+
{
7+
public function __construct(
8+
/**
9+
* The name of the prop that appears in the URL. If null, the LiveProp's field name is used.
10+
*/
11+
public readonly ?string $alias = null,
12+
) {
13+
}
14+
}

src/LiveComponent/src/Util/LiveControllerAttributesCreator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ public function attributesForRendering(MountedComponent $mounted, ComponentMetad
101101
$liveMetadata = $this->metadataFactory->getMetadata($mounted->getName());
102102

103103
if ($liveMetadata->hasQueryStringBindings()) {
104-
$queryMapping = [];
104+
$mappings = [];
105105
foreach ($liveMetadata->getAllLivePropsMetadata() as $livePropMetadata) {
106-
if ($livePropMetadata->queryStringMapping()) {
106+
if ($queryMapping = $livePropMetadata->queryStringMapping()) {
107107
$frontendName = $livePropMetadata->calculateFieldName($mounted, $livePropMetadata->getName());
108-
$queryMapping[$frontendName] = ['name' => $frontendName];
108+
$mappings[$frontendName] = ['name' => $queryMapping->alias ?? $frontendName];
109109
}
110110
}
111-
$attributesCollection->setQueryUrlMapping($queryMapping);
111+
$attributesCollection->setQueryUrlMapping($mappings);
112112
}
113113

114114
if ($isChildComponent) {

src/LiveComponent/src/Util/QueryStringPropsExtractor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function extract(Request $request, LiveComponentMetadata $metadata, objec
4545
foreach ($metadata->getAllLivePropsMetadata() as $livePropMetadata) {
4646
if ($livePropMetadata->queryStringMapping()) {
4747
$frontendName = $livePropMetadata->calculateFieldName($component, $livePropMetadata->getName());
48-
if (null !== ($value = $query[$frontendName] ?? null)) {
48+
if (null !== ($value = $query[$livePropMetadata->queryStringMapping()->alias ?? $frontendName] ?? null)) {
4949
if ('' === $value && null !== $livePropMetadata->getType() && (!$livePropMetadata->isBuiltIn() || 'array' === $livePropMetadata->getType())) {
5050
// Cast empty string to empty array for objects and arrays
5151
$value = [];

src/LiveComponent/tests/Fixtures/Component/ComponentWithUrlBoundProps.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
1515
use Symfony\UX\LiveComponent\Attribute\LiveProp;
1616
use Symfony\UX\LiveComponent\DefaultActionTrait;
17+
use Symfony\UX\LiveComponent\Metadata\QueryMapping;
1718
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address;
1819

1920
#[AsLiveComponent('component_with_url_bound_props')]
@@ -38,4 +39,9 @@ class ComponentWithUrlBoundProps
3839

3940
#[LiveProp(fieldName: 'field6', url: true)]
4041
public ?string $prop6 = null;
42+
43+
#[LiveProp(url: new QueryMapping('q'))]
44+
public ?string $prop7 = null;
45+
46+
use DefaultActionTrait;
4147
}

src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ public function testQueryStringMappingAttribute()
150150
'prop3' => ['name' => 'prop3'],
151151
'prop5' => ['name' => 'prop5'],
152152
'field6' => ['name' => 'field6'],
153+
'prop7' => ['name' => 'q'],
153154
];
154155

155156
$this->assertEquals($expected, $queryMapping);

src/LiveComponent/tests/Functional/Metadata/LiveComponentMetadataFactoryTest.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1515
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
16+
use Symfony\UX\LiveComponent\Metadata\QueryMapping;
1617
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\ComponentWithUrlBoundProps;
1718

1819
class LiveComponentMetadataFactoryTest extends KernelTestCase
@@ -30,16 +31,18 @@ public function testQueryStringMapping()
3031
$propsMetadataByName[$propMetadata->getName()] = $propMetadata;
3132
}
3233

33-
$this->assertTrue($propsMetadataByName['prop1']->queryStringMapping());
34+
$this->assertNotNull($propsMetadataByName['prop1']->queryStringMapping());
3435

35-
$this->assertTrue($propsMetadataByName['prop2']->queryStringMapping());
36+
$this->assertNotNull($propsMetadataByName['prop2']->queryStringMapping());
3637

37-
$this->assertTrue($propsMetadataByName['prop3']->queryStringMapping());
38+
$this->assertNotNull($propsMetadataByName['prop3']->queryStringMapping());
3839

39-
$this->assertFalse($propsMetadataByName['prop4']->queryStringMapping());
40+
$this->assertNull($propsMetadataByName['prop4']->queryStringMapping());
4041

41-
$this->assertTrue($propsMetadataByName['prop5']->queryStringMapping());
42+
$this->assertNotNull($propsMetadataByName['prop5']->queryStringMapping());
4243

43-
$this->assertTrue($propsMetadataByName['prop6']->queryStringMapping());
44+
$this->assertNotNull($propsMetadataByName['prop6']->queryStringMapping());
45+
46+
$this->assertEquals(new QueryMapping(alias: 'q'), $propsMetadataByName['prop7']->queryStringMapping());
4447
}
4548
}

src/LiveComponent/tests/Functional/Util/QueryStringPropsExtractorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public function getQueryStringTests(): iterable
6464
'invalid scalar value' => ['prop1[]=foo&prop1[]=bar', []],
6565
'invalid array value' => ['prop3=foo', []],
6666
'invalid object value' => ['prop5=foo', []],
67+
'aliased prop' => ['q=foo', ['prop7' => 'foo']],
6768
];
6869
}
6970
}

src/LiveComponent/tests/Unit/Metadata/LiveComponentMetadataTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ class LiveComponentMetadataTest extends TestCase
2222
public function testGetOnlyPropsThatAcceptUpdatesFromParent()
2323
{
2424
$propMetadatas = [
25-
new LivePropMetadata('noUpdateFromParent1', new LiveProp(updateFromParent: false), null, false, false, null, false),
26-
new LivePropMetadata('noUpdateFromParent2', new LiveProp(updateFromParent: false), null, false, false, null, false),
27-
new LivePropMetadata('yesUpdateFromParent1', new LiveProp(updateFromParent: true), null, false, false, null, false),
28-
new LivePropMetadata('yesUpdateFromParent2', new LiveProp(updateFromParent: true), null, false, false, null, false),
25+
new LivePropMetadata('noUpdateFromParent1', new LiveProp(updateFromParent: false), null, false, false, null, null),
26+
new LivePropMetadata('noUpdateFromParent2', new LiveProp(updateFromParent: false), null, false, false, null, null),
27+
new LivePropMetadata('yesUpdateFromParent1', new LiveProp(updateFromParent: true), null, false, false, null, null),
28+
new LivePropMetadata('yesUpdateFromParent2', new LiveProp(updateFromParent: true), null, false, false, null, null),
2929
];
3030
$liveComponentMetadata = new LiveComponentMetadata(new ComponentMetadata([]), $propMetadatas);
3131
$inputProps = [

0 commit comments

Comments
 (0)