Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/LiveComponent/src/Controller/BatchActionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ public function __construct(private HttpKernelInterface $kernel)
{
}

public function __invoke(Request $request, MountedComponent $mounted, string $serviceId, array $actions): ?Response
public function __invoke(Request $request, MountedComponent $_mounted_component, string $serviceId, array $actions): ?Response
{
$request->attributes->set('_mounted_component', $mounted);

foreach ($actions as $action) {
$name = $action['name'] ?? throw new BadRequestHttpException('Invalid JSON');

$subRequest = $request->duplicate(attributes: [
'_controller' => [$serviceId, $name],
'_component_action_args' => $action['args'] ?? [],
'_mounted_component' => $mounted,
'_mounted_component' => $_mounted_component,
'_route' => 'live_component',
]);

Expand Down
69 changes: 40 additions & 29 deletions src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Controller\BatchActionController;
use Symfony\UX\LiveComponent\LiveComponentHydrator;
use Symfony\UX\TwigComponent\ComponentFactory;
use Symfony\UX\TwigComponent\ComponentMetadata;
Expand Down Expand Up @@ -70,8 +69,7 @@ public function onKernelRequest(RequestEvent $event): void
return;
}

if (!$event->isMainRequest()) {
// sub request
if ($request->attributes->has('_controller')) {
return;
}

Expand Down Expand Up @@ -120,11 +118,12 @@ public function onKernelRequest(RequestEvent $event): void
$request->attributes->set('_controller', 'ux.live_component.batch_action_controller');
$request->attributes->set('serviceId', $metadata->getServiceId());
$request->attributes->set('actions', $data['actions']);
$request->attributes->set('mounted', $this->container->get(LiveComponentHydrator::class)->hydrate(
$request->attributes->set('_mounted_component', $this->container->get(LiveComponentHydrator::class)->hydrate(
$this->container->get(ComponentFactory::class)->get($componentName),
$data['data'],
$componentName,
));
$request->attributes->set('_is_live_batch_action', true);

return;
}
Expand All @@ -140,12 +139,12 @@ public function onKernelController(ControllerEvent $event): void
return;
}

$controller = $event->getController();

if ($controller instanceof BatchActionController) {
if ($request->attributes->get('_is_live_batch_action')) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk, just a preference - but super minor

return;
}

$controller = $event->getController();

if (!\is_array($controller) || 2 !== \count($controller)) {
throw new \RuntimeException('Not a valid live component.');
}
Expand All @@ -160,22 +159,29 @@ public function onKernelController(ControllerEvent $event): void
throw new NotFoundHttpException(sprintf('The action "%s" either doesn\'t exist or is not allowed in "%s". Make sure it exist and has the LiveAction attribute above it.', $action, \get_class($component)));
}

if ($event->isMainRequest()) {
$data = $this->parseDataFor($request);

$request->attributes->set('_component_action_args', $data['args']);
/*
* Either we:
* A) To not have a _mounted_component, so hydrate $component
* B) We DO have a _mounted_component, so no need to hydrate,
* but we DO need to make sure it's set as the controller.
*/
if (!$request->attributes->has('_mounted_component')) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once I dug in, I better understood the limitations and complexities that you were dealing with. This is really the only significant change. I wanted the "role" of onKernelController to be more straightforward - vs being the "traffic cop" that has whatever code is necessary to get the job done.

The job of onKernelController is now to do the following, and it only "acts" when the controller will be a method on the component (i.e. it's skipped for _batch):

A) Either mount the component (which is already set as the controller object. Or, if already mounted, make sure the mounted component IS the controller object.

B) Figure out the arguments to pass to the controller.

That feels more straightforward to me.

$request->attributes->set('_mounted_component', $this->container->get(LiveComponentHydrator::class)->hydrate(
$component,
$data['data'],
$this->parseDataFor($request)['data'],
$request->attributes->get('_component_name')
));
} else {
// sub-request
$event->setController([$request->attributes->get('_mounted_component')->getComponent(), $action]);
// override the component with our already-mounted version
$component = $request->attributes->get('_mounted_component')->getComponent();
$event->setController([
$component,
$action,
]);
}

$actionArguments = $request->attributes->get('_component_action_args', []);

// read the action arguments from the request, unless they're already set (batch sub-requests)
$actionArguments = $request->attributes->get('_component_action_args', $this->parseDataFor($request)['args']);
// extra variables to be made available to the controller
// (for "actions" only)
foreach (LiveArg::liveArgs($component, $action) as $parameter => $arg) {
Expand All @@ -194,21 +200,26 @@ public function onKernelController(ControllerEvent $event): void
*/
private function parseDataFor(Request $request): array
{
if ($request->query->has('data')) {
return [
'data' => json_decode($request->query->get('data'), true, 512, \JSON_THROW_ON_ERROR),
'args' => [],
'actions' => [],
];
}
if (!$request->attributes->has('_live_request_data')) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this so that we could call this method ad-hoc as many times as we want (which helps make the above methods more clear) and not worry about performance.


if ($request->query->has('data')) {
return [
'data' => json_decode($request->query->get('data'), true, 512, \JSON_THROW_ON_ERROR),
'args' => [],
'actions' => [],
];
}

$requestData = json_decode($request->getContent(), true, 512, \JSON_THROW_ON_ERROR);
$requestData = json_decode($request->getContent(), true, 512, \JSON_THROW_ON_ERROR);

return [
'data' => $requestData['data'] ?? [],
'args' => $requestData['args'] ?? [],
'actions' => $requestData['actions'] ?? [],
];
$request->attributes->set('_live_request_data', [
'data' => $requestData['data'] ?? [],
'args' => $requestData['args'] ?? [],
'actions' => $requestData['actions'] ?? [],
]);
}

return $request->attributes->get('_live_request_data');
}

public function onKernelView(ViewEvent $event): void
Expand Down