Skip to content

Commit 769874e

Browse files
authored
Merge pull request #12 from Kocal/LiveListenerMethodsShouldBePublicRule
2 parents 970ab67 + 3f85d83 commit 769874e

File tree

8 files changed

+257
-0
lines changed

8 files changed

+257
-0
lines changed

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,68 @@ final class TodoList
9292

9393
<br>
9494

95+
### LiveListenerMethodsShouldBePublicRule
96+
97+
Enforces that all methods annotated with `#[LiveListener]` in LiveComponents must be declared as public.
98+
LiveListener methods need to be publicly accessible to be invoked when listening to events from the frontend.
99+
100+
```yaml
101+
rules:
102+
- Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule
103+
```
104+
105+
```php
106+
// src/Twig/Components/Notification.php
107+
namespace App\Twig\Components;
108+
109+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
110+
use Symfony\UX\LiveComponent\Attribute\LiveListener;
111+
112+
#[AsLiveComponent]
113+
final class Notification
114+
{
115+
#[LiveListener('notification:received')]
116+
private function onNotificationReceived(): void
117+
{
118+
}
119+
120+
#[LiveListener('notification:dismissed')]
121+
protected function onNotificationDismissed(): void
122+
{
123+
}
124+
}
125+
```
126+
127+
:x:
128+
129+
<br>
130+
131+
```php
132+
// src/Twig/Components/Notification.php
133+
namespace App\Twig\Components;
134+
135+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
136+
use Symfony\UX\LiveComponent\Attribute\LiveListener;
137+
138+
#[AsLiveComponent]
139+
final class Notification
140+
{
141+
#[LiveListener('notification:received')]
142+
public function onNotificationReceived(): void
143+
{
144+
}
145+
146+
#[LiveListener('notification:dismissed')]
147+
public function onNotificationDismissed(): void
148+
{
149+
}
150+
}
151+
```
152+
153+
:+1:
154+
155+
<br>
156+
95157
## TwigComponent Rules
96158

97159
> [!NOTE]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Rules\LiveComponent;
6+
7+
use Kocal\PHPStanSymfonyUX\NodeAnalyzer\AttributeFinder;
8+
use PhpParser\Node;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
14+
use Symfony\UX\LiveComponent\Attribute\LiveListener;
15+
16+
/**
17+
* @implements Rule<Class_>
18+
*/
19+
final class LiveListenerMethodsShouldBePublicRule implements Rule
20+
{
21+
public function getNodeType(): string
22+
{
23+
return Class_::class;
24+
}
25+
26+
public function processNode(Node $node, Scope $scope): array
27+
{
28+
if (! AttributeFinder::findAnyAttribute($node, [AsLiveComponent::class])) {
29+
return [];
30+
}
31+
32+
$errors = [];
33+
34+
foreach ($node->getMethods() as $method) {
35+
if (! AttributeFinder::findAnyAttribute($method, [LiveListener::class])) {
36+
continue;
37+
}
38+
39+
if (! $method->isPublic()) {
40+
$errors[] = RuleErrorBuilder::message(sprintf(
41+
'LiveListener method "%s()" should be public.',
42+
$method->name->toString()
43+
))
44+
->identifier('symfonyUX.liveComponent.liveListenerMethodShouldBePublic')
45+
->line($method->getLine())
46+
->tip('Change the method visibility to public.')
47+
->build();
48+
}
49+
}
50+
51+
return $errors;
52+
}
53+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
9+
#[AsLiveComponent]
10+
final class NoLiveListener
11+
{
12+
protected function someProtectedMethod(): void
13+
{
14+
}
15+
16+
private function somePrivateMethod(): void
17+
{
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\LiveListener;
8+
9+
final class NotAComponent
10+
{
11+
#[LiveListener('some.event')]
12+
private function onSomeEvent(): void
13+
{
14+
}
15+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
use Symfony\UX\LiveComponent\Attribute\LiveListener;
9+
10+
#[AsLiveComponent]
11+
final class PrivateLiveListener
12+
{
13+
#[LiveListener('another.event')]
14+
protected function onAnotherEvent(): void
15+
{
16+
}
17+
18+
#[LiveListener('some.event')]
19+
private function onSomeEvent(): void
20+
{
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
use Symfony\UX\LiveComponent\Attribute\LiveListener;
9+
10+
#[AsLiveComponent]
11+
final class PublicLiveListener
12+
{
13+
#[LiveListener('some.event')]
14+
public function onSomeEvent(): void
15+
{
16+
}
17+
18+
#[LiveListener('another.event')]
19+
public function onAnotherEvent(): void
20+
{
21+
}
22+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule;
6+
7+
use Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
/**
12+
* @extends RuleTestCase<LiveListenerMethodsShouldBePublicRule>
13+
*/
14+
final class LiveListenerMethodsShouldBePublicRuleTest extends RuleTestCase
15+
{
16+
public function testViolations(): void
17+
{
18+
$this->analyse(
19+
[__DIR__ . '/Fixture/PrivateLiveListener.php'],
20+
[
21+
[
22+
'LiveListener method "onAnotherEvent()" should be public.',
23+
13,
24+
'Change the method visibility to public.',
25+
],
26+
[
27+
'LiveListener method "onSomeEvent()" should be public.',
28+
18,
29+
'Change the method visibility to public.',
30+
],
31+
]
32+
);
33+
}
34+
35+
public function testNoViolations(): void
36+
{
37+
$this->analyse(
38+
[__DIR__ . '/Fixture/NotAComponent.php'],
39+
[]
40+
);
41+
42+
$this->analyse(
43+
[__DIR__ . '/Fixture/PublicLiveListener.php'],
44+
[]
45+
);
46+
47+
$this->analyse(
48+
[__DIR__ . '/Fixture/NoLiveListener.php'],
49+
[]
50+
);
51+
}
52+
53+
public static function getAdditionalConfigFiles(): array
54+
{
55+
return [__DIR__ . '/config/configured_rule.neon'];
56+
}
57+
58+
protected function getRule(): Rule
59+
{
60+
return self::getContainer()->getByType(LiveListenerMethodsShouldBePublicRule::class);
61+
}
62+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule

0 commit comments

Comments
 (0)