Skip to content

Commit cfc2f59

Browse files
[Turbo] Add Helper/TurboStream::append() et al. methods
1 parent f96a697 commit cfc2f59

File tree

7 files changed

+295
-2
lines changed

7 files changed

+295
-2
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## 2.21.0
4+
5+
- Add `Helper/TurboStream::append()` et al. methods
6+
- Add `TurboStreamResponse`
7+
38
## 2.19.0
49

510
- Fix Doctrine proxies are not Broadcasted #3139

doc/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ Let's discover how to use Turbo Streams to enhance your `Symfony forms`_::
374374
{% endblock %}
375375

376376
Supported actions are ``append``, ``prepend``, ``replace``, ``update``,
377-
``remove``, ``before`` and ``after``.
377+
``remove``, ``before``, ``after`` and ``refresh``.
378378
`Read the Turbo Streams documentation for more details`_.
379379

380380
Resetting the Form

phpstan.neon.dist

+5
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ parameters:
4545
message: "#^Call to an undefined method Doctrine\\\\ORM\\\\Event\\\\PostFlushEventArgs\\:\\:getEntityManager\\(\\)\\.$#"
4646
count: 1
4747
path: src/Doctrine/BroadcastListener.php
48+
49+
-
50+
message: "#^Method Symfony\\\\UX\\\\Turbo\\\\TurboStreamResponse::__construct\\(\\) has parameter \\$headers with no value type specified in iterable type array\\.$#"
51+
count: 1
52+
path: src/TurboStreamResponse.php

src/Helper/TurboStream.php

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Turbo\Helper;
13+
14+
/**
15+
* @see https://turbo.hotwired.dev/reference/streams
16+
*/
17+
final class TurboStream
18+
{
19+
/**
20+
* Appends to the element(s) designated by the target CSS selector.
21+
*/
22+
public static function append(string $target, string $html): string
23+
{
24+
return self::wrap('append', $target, $html);
25+
}
26+
27+
/**
28+
* Prepends to the element(s) designated by the target CSS selector.
29+
*/
30+
public static function prepend(string $target, string $html): string
31+
{
32+
return self::wrap('prepend', $target, $html);
33+
}
34+
35+
/**
36+
* Replaces the element(s) designated by the target CSS selector.
37+
*/
38+
public static function replace(string $target, string $html, bool $morph = false): string
39+
{
40+
return self::wrap('replace', $target, $html, $morph ? ' method="morph"' : '');
41+
}
42+
43+
/**
44+
* Updates the content of the element(s) designated by the target CSS selector.
45+
*/
46+
public static function update(string $target, string $html, bool $morph = false): string
47+
{
48+
return self::wrap('update', $target, $html, $morph ? ' method="morph"' : '');
49+
}
50+
51+
/**
52+
* Removes the element(s) designated by the target CSS selector.
53+
*/
54+
public static function remove(string $target): string
55+
{
56+
return \sprintf('<turbo-stream action="remove" targets="%s"></turbo-stream>', htmlspecialchars($target));
57+
}
58+
59+
/**
60+
* Inserts before the element(s) designated by the target CSS selector.
61+
*/
62+
public static function before(string $target, string $html): string
63+
{
64+
return self::wrap('before', $target, $html);
65+
}
66+
67+
/**
68+
* Inserts after the element(s) designated by the target CSS selector.
69+
*/
70+
public static function after(string $target, string $html): string
71+
{
72+
return self::wrap('after', $target, $html);
73+
}
74+
75+
/**
76+
* Initiates a Page Refresh to render new content with morphing.
77+
*
78+
* @see Initiates a Page Refresh to render new content with morphing.
79+
*/
80+
public static function refresh(?string $requestId = null): string
81+
{
82+
if (null === $requestId) {
83+
return '<turbo-stream action="refresh"></turbo-stream>';
84+
}
85+
86+
return \sprintf('<turbo-stream action="refresh" request-id="%s"></turbo-stream>', htmlspecialchars($requestId));
87+
}
88+
89+
private static function wrap(string $action, string $target, string $html, string $attr = ''): string
90+
{
91+
return \sprintf(<<<EOHTML
92+
<turbo-stream action="%s" targets="%s"%s>
93+
<template>%s</template>
94+
</turbo-stream>
95+
EOHTML, $action, htmlspecialchars($target), $attr, $html);
96+
}
97+
}

src/TurboStreamResponse.php

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Turbo;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\UX\Turbo\Helper\TurboStream;
16+
17+
class TurboStreamResponse extends Response
18+
{
19+
public function __construct(?string $content = '', int $status = 200, array $headers = [])
20+
{
21+
parent::__construct($content, $status, $headers);
22+
23+
if (!$this->headers->has('Content-Type')) {
24+
$this->headers->set('Content-Type', TurboBundle::STREAM_MEDIA_TYPE);
25+
}
26+
}
27+
28+
/**
29+
* @return $this
30+
*/
31+
public function append(string $target, string $html): static
32+
{
33+
$this->setContent($this->getContent().TurboStream::append($target, $html));
34+
35+
return $this;
36+
}
37+
38+
/**
39+
* @return $this
40+
*/
41+
public function prepend(string $target, string $html): static
42+
{
43+
$this->setContent($this->getContent().TurboStream::prepend($target, $html));
44+
45+
return $this;
46+
}
47+
48+
/**
49+
* @return $this
50+
*/
51+
public function replace(string $target, string $html, bool $morph = false): static
52+
{
53+
$this->setContent($this->getContent().TurboStream::replace($target, $html, $morph));
54+
55+
return $this;
56+
}
57+
58+
/**
59+
* @return $this
60+
*/
61+
public function update(string $target, string $html, bool $morph = false): static
62+
{
63+
$this->setContent($this->getContent().TurboStream::update($target, $html, $morph));
64+
65+
return $this;
66+
}
67+
68+
/**
69+
* @return $this
70+
*/
71+
public function remove(string $target): static
72+
{
73+
$this->setContent($this->getContent().TurboStream::remove($target));
74+
75+
return $this;
76+
}
77+
78+
/**
79+
* @return $this
80+
*/
81+
public function before(string $target, string $html): static
82+
{
83+
$this->setContent($this->getContent().TurboStream::before($target, $html));
84+
85+
return $this;
86+
}
87+
88+
/**
89+
* @return $this
90+
*/
91+
public function after(string $target, string $html): static
92+
{
93+
$this->setContent($this->getContent().TurboStream::after($target, $html));
94+
95+
return $this;
96+
}
97+
98+
/**
99+
* @return $this
100+
*/
101+
public function refresh(?string $requestId = null): static
102+
{
103+
$this->setContent($this->getContent().TurboStream::refresh($requestId));
104+
105+
return $this;
106+
}
107+
}

tests/Helper/TurboStreamTest.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Turbo\Tests\Helper;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\UX\Turbo\Helper\TurboStream;
16+
17+
class TurboStreamTest extends TestCase
18+
{
19+
/**
20+
* @testWith ["append"]
21+
* ["prepend"]
22+
* ["replace"]
23+
* ["update"]
24+
* ["before"]
25+
* ["after"]
26+
*/
27+
public function testStream(string $action): void
28+
{
29+
$this->assertSame(<<<EOHTML
30+
<turbo-stream action="{$action}" targets="some[&quot;selector&quot;]">
31+
<template><div>content</div></template>
32+
</turbo-stream>
33+
EOHTML,
34+
TurboStream::$action('some["selector"]', '<div>content</div>')
35+
);
36+
}
37+
38+
/**
39+
* @testWith ["replace"]
40+
* ["update"]
41+
*/
42+
public function testStreamMorph(string $action): void
43+
{
44+
$this->assertSame(<<<EOHTML
45+
<turbo-stream action="{$action}" targets="some[&quot;selector&quot;]" method="morph">
46+
<template><div>content</div></template>
47+
</turbo-stream>
48+
EOHTML,
49+
TurboStream::$action('some["selector"]', '<div>content</div>', morph: true)
50+
);
51+
}
52+
53+
public function testRemove(): void
54+
{
55+
$this->assertSame(<<<EOHTML
56+
<turbo-stream action="remove" targets="some[&quot;selector&quot;]"></turbo-stream>
57+
EOHTML,
58+
TurboStream::remove('some["selector"]')
59+
);
60+
}
61+
62+
public function testRefreshWithoutId(): void
63+
{
64+
$this->assertSame(<<<EOHTML
65+
<turbo-stream action="refresh"></turbo-stream>
66+
EOHTML,
67+
TurboStream::refresh()
68+
);
69+
}
70+
71+
public function testRefreshWithId(): void
72+
{
73+
$this->assertSame(<<<EOHTML
74+
<turbo-stream action="refresh" request-id="a&quot;b"></turbo-stream>
75+
EOHTML,
76+
TurboStream::refresh('a"b')
77+
);
78+
}
79+
}

tests/app/Kernel.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public function songs(Request $request, EntityManagerInterface $doctrine, Enviro
220220
$song->artist = $doctrine->find(Artist::class, $artistId);
221221
}
222222
}
223-
if ($remove = $request->get('remove')) {
223+
if ($request->get('remove')) {
224224
$doctrine->remove($song);
225225
} else {
226226
$doctrine->persist($song);

0 commit comments

Comments
 (0)