Skip to content

Commit f3a489e

Browse files
committed
[Turbo] Fixing a bug where saving a proxy would not trigger Broadcasts
1 parent e240c7d commit f3a489e

File tree

6 files changed

+104
-7
lines changed

6 files changed

+104
-7
lines changed

src/Bridge/Mercure/Broadcaster.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\UX\Turbo\Bridge\Mercure;
1313

14+
use Doctrine\Common\Util\ClassUtils;
1415
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1516
use Symfony\Component\Mercure\HubInterface;
1617
use Symfony\Component\Mercure\Update;
18+
use Symfony\Component\VarExporter\LazyObjectInterface;
1719
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
1820

1921
/**
@@ -61,7 +63,14 @@ public function broadcast(object $entity, string $action, array $options): void
6163
return;
6264
}
6365

64-
$entityClass = $entity::class;
66+
if ($entity instanceof LazyObjectInterface) {
67+
$entityClass = get_parent_class($entity);
68+
if (false === $entityClass) {
69+
throw new \LogicException('Parent class missing');
70+
}
71+
} else {
72+
$entityClass = ClassUtils::getClass($entity);
73+
}
6574

6675
if (!isset($options['rendered_action'])) {
6776
throw new \InvalidArgumentException(sprintf('Cannot broadcast entity of class "%s" as option "rendered_action" is missing.', $entityClass));

src/Broadcaster/TwigBroadcaster.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\UX\Turbo\Broadcaster;
1313

14+
use Doctrine\Common\Util\ClassUtils;
15+
use Symfony\Component\VarExporter\LazyObjectInterface;
1416
use Twig\Environment;
1517

1618
/**
@@ -42,8 +44,18 @@ public function broadcast(object $entity, string $action, array $options): void
4244
$options['id'] = $id;
4345
}
4446

47+
// handle proxies (both styles)
48+
if ($entity instanceof LazyObjectInterface) {
49+
$class = get_parent_class($entity);
50+
if (false === $class) {
51+
throw new \LogicException('Parent class missing');
52+
}
53+
} else {
54+
$class = ClassUtils::getClass($entity);
55+
}
56+
4557
if (null === $template = $options['template'] ?? null) {
46-
$template = $entity::class;
58+
$template = $class;
4759
foreach ($this->templatePrefixes as $namespace => $prefix) {
4860
if (str_starts_with($template, $namespace)) {
4961
$template = substr_replace($template, $prefix, 0, \strlen($namespace));

src/Doctrine/BroadcastListener.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
use Doctrine\Common\Annotations\Reader;
1515
use Doctrine\Common\EventArgs;
16+
use Doctrine\Common\Util\ClassUtils;
1617
use Doctrine\ORM\EntityManagerInterface;
1718
use Doctrine\ORM\Event\OnFlushEventArgs;
1819
use Doctrine\ORM\Event\PostFlushEventArgs;
20+
use Symfony\Component\VarExporter\LazyObjectInterface;
1921
use Symfony\Contracts\Service\ResetInterface;
2022
use Symfony\UX\Turbo\Attribute\Broadcast;
2123
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
@@ -126,7 +128,15 @@ public function reset(): void
126128

127129
private function storeEntitiesToPublish(EntityManagerInterface $em, object $entity, string $property): void
128130
{
129-
$class = $entity::class;
131+
// handle proxies (both styles)
132+
if ($entity instanceof LazyObjectInterface) {
133+
$class = get_parent_class($entity);
134+
if (false === $class) {
135+
throw new \LogicException('Parent class missing');
136+
}
137+
} else {
138+
$class = ClassUtils::getClass($entity);
139+
}
130140

131141
if (!isset($this->broadcastedClasses[$class])) {
132142
$this->broadcastedClasses[$class] = [];

tests/BroadcastTest.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ protected function setUp(): void
3333
parent::setUp();
3434
}
3535

36-
public function testBroadcast(): void
36+
public function testBroadcastBasic(): void
3737
{
3838
($client = self::createPantherClient())->request('GET', '/books');
3939

@@ -88,4 +88,20 @@ public function testExpressionLanguageBroadcast(): void
8888
'Artist 2 shows a song that does not belong to them'
8989
);
9090
}
91+
92+
public function testBroadcastWithProxy(): void
93+
{
94+
// testing that Artist is updated, even though it's saved as Proxy
95+
($client = self::createPantherClient())->request('GET', '/artistFromSong');
96+
97+
// submit first time to create the artist
98+
$client->submitForm('Submit');
99+
$this->assertSelectorWillContain('#artists', 'testing artist');
100+
101+
// submit again to update the artist
102+
$client->submitForm('Submit');
103+
$this->assertSelectorWillContain('#artists', 'testing artist after change');
104+
// this part is from the stream template
105+
$this->assertSelectorWillContain('#artists', ', updated)');
106+
}
91107
}

tests/app/Kernel.php

+39-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
use Symfony\Component\HttpFoundation\Response;
3434
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
3535
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
36-
use Symfony\Component\Mercure\Hub;
3736
use Symfony\Component\Mercure\HubInterface;
3837
use Symfony\Component\Mercure\Update;
3938
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
@@ -117,8 +116,6 @@ protected function configureContainer(ContainerConfigurator $container): void
117116
],
118117
],
119118
]);
120-
121-
$container->services()->alias(Hub::class, 'mercure.hub.default'); // FIXME: temporary fix for a bug in https://github.com/symfony/mercure-bundle/pull/42
122119
}
123120

124121
protected function configureRoutes(RoutingConfigurator $routes): void
@@ -135,6 +132,7 @@ protected function configureRoutes(RoutingConfigurator $routes): void
135132
$routes->add('songs', '/songs')->controller('kernel::songs');
136133
$routes->add('artists', '/artists')->controller('kernel::artists');
137134
$routes->add('artist', '/artists/{id}')->controller('kernel::artist');
135+
$routes->add('artist_from_song', '/artistFromSong')->controller('kernel::artistFromSong');
138136
}
139137

140138
public function getProjectDir(): string
@@ -265,4 +263,42 @@ public function artist(Request $request, EntityManagerInterface $doctrine, Envir
265263

266264
return new Response($twig->render('artist.html.twig', ['artist' => $artist]));
267265
}
266+
267+
public function artistFromSong(Request $request, EntityManagerInterface $doctrine, Environment $twig): Response
268+
{
269+
$song = null;
270+
if ($request->isMethod('POST')) {
271+
// on first post, create the objects
272+
// on second, update the artist
273+
$id = $request->get('id');
274+
if (!$id) {
275+
$artist = new Artist();
276+
$artist->name = 'testing artist';
277+
278+
$song = new Song();
279+
$song->artist = $artist;
280+
$song->title = 'testing song title';
281+
282+
$doctrine->persist($artist);
283+
$doctrine->persist($song);
284+
$doctrine->flush();
285+
} else {
286+
$song = $doctrine->find(Song::class, $id);
287+
if (!$song) {
288+
throw new NotFoundHttpException();
289+
}
290+
$artist = $song->artist;
291+
if (!$artist) {
292+
throw new NotFoundHttpException();
293+
}
294+
$artist->name = $artist->name.' after change';
295+
296+
$doctrine->flush();
297+
}
298+
}
299+
300+
return new Response($twig->render('artist_from_song.html.twig', [
301+
'song' => $song,
302+
]));
303+
}
268304
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% extends 'base.html.twig' %}
2+
3+
{% block body %}
4+
<h1>Update Artist Via Song</h1>
5+
6+
<div id="artists" {{ turbo_stream_listen('App\\Entity\\Artist') }}></div>
7+
8+
<turbo-frame id="api">
9+
<form method="post" enctype="application/x-www-form-urlencoded">
10+
<input name="id" value="{{ song ? song.id }}">
11+
<button>Submit</button>
12+
</form>
13+
</turbo-frame>
14+
{% endblock %}

0 commit comments

Comments
 (0)