Skip to content

Commit 27e2be9

Browse files
committed
feature #562 [Live] Allow to edit component route (simondaigre, kbond)
This PR was merged into the 2.x branch. Discussion ---------- [Live] Allow to edit component route | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Tickets | Fix #514 | License | MIT Added a new `route` parameter to `AsLiveComponent`, which allows to choose another route for Ajax calls. Commits ------- bbd57ff add tests 0707b30 fix formatting 6534689 fix BatchActionController aa49e1d [Live] Allow to edit component route 748220e [Live] Allow to edit component route e075298 [Live] Allow to edit component route 9836c18 [Live] Allow to edit component route 0ceae7e [Live] Allow to edit component route f55cec2 [Live] Allow to edit component route
2 parents 7f73375 + bbd57ff commit 27e2be9

File tree

15 files changed

+152
-9
lines changed

15 files changed

+152
-9
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## 2.7.0
4+
5+
- Added a new `route` parameter to `AsLiveComponent`, which allows to choose
6+
another route for Ajax calls.
7+
38
## 2.6.0
49

510
- [BC BREAK]: The path to `live_component.xml` changed _and_ the import now

src/LiveComponent/config/routes.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
44

55
return function (RoutingConfigurator $routes) {
6-
$routes->add('ux_live_component', '/{component}/{action}')
6+
$routes->add('ux_live_component', '/{_live_component}/{_live_action}')
77
->defaults([
8-
'action' => 'get',
8+
'_live_action' => 'get',
99
])
1010
;
1111
};

src/LiveComponent/doc/index.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,6 +2114,39 @@ To handle this, add the ``data-live-ignore`` attribute to the element:
21142114
``data-live-id`` attribute. During a re-render, if this value changes, all
21152115
of the children of the element will be re-rendered, even those with ``data-live-ignore``.
21162116

2117+
Define another route for your Component
2118+
---------------------------------------
2119+
2120+
The default route for live components is ``/components/{_live_component}/{_live_action}``
2121+
If you have multiples firewalls in your app, you may want to specify another route for your component.
2122+
It may be useful if you want to get the current logged in user in your component.
2123+
2124+
To use another route, you need to declare it :
2125+
2126+
.. code-block:: yaml
2127+
2128+
# config/routes/attributes.yaml
2129+
live_component_admin:
2130+
path: /admin/_components/{_live_component}/{_live_action}
2131+
defaults:
2132+
_live_action: 'get'
2133+
2134+
then specify this new route to your component :
2135+
2136+
.. code-block:: diff
2137+
2138+
// src/Components/RandomNumberComponent.php
2139+
2140+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
2141+
use Symfony\UX\LiveComponent\DefaultActionTrait;
2142+
2143+
- #[AsLiveComponent('random_number')]
2144+
+ #[AsLiveComponent('random_number', route: 'live_component_admin')]
2145+
class RandomNumberComponent
2146+
{
2147+
use DefaultActionTrait;
2148+
}
2149+
21172150
Backward Compatibility promise
21182151
------------------------------
21192152

src/LiveComponent/src/Attribute/AsLiveComponent.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public function __construct(
2828
bool $exposePublicProps = true,
2929
string $attributesVar = 'attributes',
3030
public bool $csrf = true,
31+
public string $route = 'ux_live_component'
3132
) {
3233
parent::__construct($name, $template, $exposePublicProps, $attributesVar);
3334
}
@@ -41,6 +42,7 @@ public function serviceConfig(): array
4142
'default_action' => $this->defaultAction,
4243
'live' => true,
4344
'csrf' => $this->csrf,
45+
'route' => $this->route,
4446
]);
4547
}
4648

src/LiveComponent/src/Controller/BatchActionController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function __invoke(Request $request, MountedComponent $_mounted_component,
3737
'_controller' => [$serviceId, $name],
3838
'_component_action_args' => $action['args'] ?? [],
3939
'_mounted_component' => $_mounted_component,
40-
'_route' => 'ux_live_component',
40+
'_live_component' => $serviceId,
4141
]);
4242

4343
$response = $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);

src/LiveComponent/src/EventListener/AddLiveAttributesSubscriber.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public static function getSubscribedServices(): array
9292
private function getLiveAttributes(MountedComponent $mounted, ComponentMetadata $metadata): ComponentAttributes
9393
{
9494
$name = $mounted->getName();
95-
$url = $this->container->get(UrlGeneratorInterface::class)->generate('ux_live_component', ['component' => $name]);
95+
$url = $this->container->get(UrlGeneratorInterface::class)->generate($metadata->get('route'), ['_live_component' => $name]);
9696
/** @var DehydratedComponent $dehydratedComponent */
9797
$dehydratedComponent = $this->container->get(LiveComponentHydrator::class)->dehydrate($mounted);
9898
/** @var TwigAttributeHelper $helper */

src/LiveComponent/src/EventListener/LiveComponentSubscriber.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public function onKernelRequest(RequestEvent $event): void
7676
}
7777

7878
// the default "action" is get, which does nothing
79-
$action = $request->attributes->get('action', 'get');
80-
$componentName = (string) $request->attributes->get('component');
79+
$action = $request->attributes->get('_live_action', 'get');
80+
$componentName = (string) $request->attributes->get('_live_component');
8181

8282
$request->attributes->set('_component_name', $componentName);
8383

@@ -313,7 +313,7 @@ private function createResponse(MountedComponent $mounted): Response
313313

314314
private function isLiveComponentRequest(Request $request): bool
315315
{
316-
return 'ux_live_component' === $request->attributes->get('_route');
316+
return $request->attributes->has('_live_component');
317317
}
318318

319319
private function hydrateComponent(object $component, string $componentName, Request $request): MountedComponent

src/LiveComponent/src/Twig/LiveComponentRuntime.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ public function getComponentUrl(string $name, array $props = []): string
3535
{
3636
$mounted = $this->factory->create($name, $props);
3737
$dehydratedComponent = $this->hydrator->dehydrate($mounted);
38-
$params = ['component' => $name] + $dehydratedComponent->all();
38+
$params = ['_live_component' => $name] + $dehydratedComponent->all();
3939

40-
return $this->urlGenerator->generate('ux_live_component', $params);
40+
$metadata = $this->factory->metadataFor($mounted->getName());
41+
42+
return $this->urlGenerator->generate($metadata->get('route'), $params);
4143
}
4244
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
4+
5+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
6+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
7+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
8+
use Symfony\UX\LiveComponent\DefaultActionTrait;
9+
10+
/**
11+
* @author Kevin Bond <[email protected]>
12+
*/
13+
#[AsLiveComponent('alternate_route', route: 'alternate_live_route')]
14+
final class AlternateRoute
15+
{
16+
use DefaultActionTrait;
17+
18+
#[LiveProp]
19+
public int $count = 0;
20+
21+
#[LiveAction]
22+
public function increase(): void
23+
{
24+
++$this->count;
25+
}
26+
}

src/LiveComponent/tests/Fixtures/Kernel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,6 @@ protected function configureRoutes(RoutingConfigurator $routes): void
122122

123123
$routes->add('template', '/render-template/{template}')->controller('kernel::renderTemplate');
124124
$routes->add('homepage', '/')->controller('kernel::index');
125+
$routes->add('alternate_live_route', '/alt/{_live_component}/{_live_action}')->defaults(['_live_action' => 'get']);
125126
}
126127
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
{{ component_url('component1', { prop1: null, prop2: date }) }}
2+
{{ component_url('alternate_route') }}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div{{ attributes }}>
2+
From alternate route. (count: {{ count }})
3+
</div>

src/LiveComponent/tests/Functional/Controller/BatchActionControllerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,38 @@ public function testCanBatchActions(): void
7474
;
7575
}
7676

77+
public function testCanBatchActionsWithAlternateRoute(): void
78+
{
79+
$dehydrated = $this->dehydrateComponent($this->mountComponent('alternate_route'))->all();
80+
81+
$this->browser()
82+
->throwExceptions()
83+
->get('/alt/alternate_route', ['json' => ['data' => $dehydrated]])
84+
->assertSuccessful()
85+
->assertSee('count: 0')
86+
->use(function (Crawler $crawler, KernelBrowser $browser) {
87+
$rootElement = $crawler->filter('div')->first();
88+
$liveData = json_decode($rootElement->attr('data-live-data-value'), true);
89+
$liveProps = json_decode($rootElement->attr('data-live-props-value'), true);
90+
91+
$browser->post('/alt/alternate_route/_batch', [
92+
'json' => [
93+
'data' => $liveData + $liveProps,
94+
'actions' => [
95+
['name' => 'increase'],
96+
['name' => 'increase'],
97+
['name' => 'increase'],
98+
],
99+
],
100+
'headers' => ['X-CSRF-TOKEN' => $rootElement->attr('data-live-csrf-value')],
101+
]);
102+
})
103+
->assertOn('/alt/alternate_route/_batch')
104+
->assertSuccessful()
105+
->assertSee('count: 3')
106+
;
107+
}
108+
77109
public function testCsrfTokenIsChecked(): void
78110
{
79111
$dehydrated = $this->dehydrateComponent($this->mountComponent('with_actions'))->all();

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ public function testCanRenderComponentAsHtml(): void
5555
;
5656
}
5757

58+
public function testCanRenderComponentAsHtmlWithAlternateRoute(): void
59+
{
60+
$dehydrated = $this->dehydrateComponent($this->mountComponent('alternate_route'))->all();
61+
62+
$this->browser()
63+
->throwExceptions()
64+
->get('/alt/alternate_route?data='.urlencode(json_encode($dehydrated)))
65+
->assertSuccessful()
66+
->assertOn('/alt/alternate_route', parts: ['path'])
67+
->assertContains('From alternate route. (count: 0)')
68+
;
69+
}
70+
5871
public function testCanExecuteComponentAction(): void
5972
{
6073
$dehydrated = $this->dehydrateComponent($this->mountComponent('component2'))->all();
@@ -80,6 +93,30 @@ public function testCanExecuteComponentAction(): void
8093
;
8194
}
8295

96+
public function testCanExecuteComponentActionWithAlternateRoute(): void
97+
{
98+
$dehydrated = $this->dehydrateComponent($this->mountComponent('alternate_route'))->all();
99+
$token = null;
100+
101+
$this->browser()
102+
->throwExceptions()
103+
->get('/alt/alternate_route?data='.urlencode(json_encode($dehydrated)))
104+
->assertSuccessful()
105+
->assertContains('count: 0')
106+
->use(function (Crawler $crawler) use (&$token) {
107+
// get a valid token to use for actions
108+
$token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
109+
})
110+
->post('/alt/alternate_route/increase', [
111+
'headers' => ['X-CSRF-TOKEN' => $token],
112+
'body' => json_encode(['data' => $dehydrated]),
113+
])
114+
->assertSuccessful()
115+
->assertOn('/alt/alternate_route/increase')
116+
->assertContains('count: 1')
117+
;
118+
}
119+
83120
public function testCannotExecuteComponentActionForGetRequest(): void
84121
{
85122
$this->browser()

src/LiveComponent/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?prop2=2022-10-06T00:00:00', $rendered);
2828
$this->assertStringContainsString('_checksum=', $rendered);
29+
$this->assertStringContainsString('/alt/alternate_route?', $rendered);
2930
}
3031
}

0 commit comments

Comments
 (0)