Skip to content

Commit 970ab67

Browse files
authored
Merge pull request #11 from Kocal/LiveActionMethodsShouldBePublicRule
2 parents c00be8e + 7152161 commit 970ab67

File tree

9 files changed

+295
-0
lines changed

9 files changed

+295
-0
lines changed

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,82 @@ After installing the package, you need to configure PHPStan to use the rules.
1616

1717
Each rule can be enabled individually by adding it to your `phpstan.dist.neon` configuration file.
1818

19+
## LiveComponent Rules
20+
21+
### LiveActionMethodsShouldBePublicRule
22+
23+
Enforces that all methods annotated with `#[LiveAction]` in LiveComponents must be declared as public.
24+
LiveAction methods need to be publicly accessible to be invoked as component actions from the frontend.
25+
26+
```yaml
27+
rules:
28+
- Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule
29+
```
30+
31+
```php
32+
// src/Twig/Components/TodoList.php
33+
namespace App\Twig\Components;
34+
35+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
36+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
37+
38+
#[AsLiveComponent]
39+
final class TodoList
40+
{
41+
#[LiveAction]
42+
private function addItem(): void
43+
{
44+
}
45+
}
46+
```
47+
48+
```php
49+
// src/Twig/Components/TodoList.php
50+
namespace App\Twig\Components;
51+
52+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
53+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
54+
55+
#[AsLiveComponent]
56+
final class TodoList
57+
{
58+
#[LiveAction]
59+
protected function deleteItem(): void
60+
{
61+
}
62+
}
63+
```
64+
65+
:x:
66+
67+
<br>
68+
69+
```php
70+
// src/Twig/Components/TodoList.php
71+
namespace App\Twig\Components;
72+
73+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
74+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
75+
76+
#[AsLiveComponent]
77+
final class TodoList
78+
{
79+
#[LiveAction]
80+
public function addItem(): void
81+
{
82+
}
83+
84+
#[LiveAction]
85+
public function deleteItem(): void
86+
{
87+
}
88+
}
89+
```
90+
91+
:+1:
92+
93+
<br>
94+
1995
## TwigComponent Rules
2096

2197
> [!NOTE]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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\LiveAction;
15+
16+
/**
17+
* @implements Rule<Class_>
18+
*/
19+
final class LiveActionMethodsShouldBePublicRule 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, [LiveAction::class])) {
36+
continue;
37+
}
38+
39+
if (! $method->isPublic()) {
40+
$methodName = $method->name->toString();
41+
42+
$errors[] = RuleErrorBuilder::message(
43+
sprintf('LiveAction method "%s()" should be public.', $methodName)
44+
)
45+
->identifier('symfonyUX.liveComponent.liveActionMethodsShouldBePublic')
46+
->line($method->getLine())
47+
->tip('Methods annotated with #[LiveAction] must be public to be accessible as component actions.')
48+
->build();
49+
}
50+
}
51+
52+
return $errors;
53+
}
54+
}
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\LiveActionMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
9+
10+
#[AsLiveComponent]
11+
final class LiveComponentWithPrivateLiveAction
12+
{
13+
public string $name = '';
14+
15+
#[LiveAction]
16+
private function save(): void
17+
{
18+
}
19+
}
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\LiveActionMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
9+
10+
#[AsLiveComponent]
11+
final class LiveComponentWithProtectedLiveAction
12+
{
13+
public string $email = '';
14+
15+
#[LiveAction]
16+
protected function delete(): void
17+
{
18+
}
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
9+
10+
#[AsLiveComponent]
11+
final class LiveComponentWithPublicLiveAction
12+
{
13+
public string $title = '';
14+
15+
#[LiveAction]
16+
public function submit(): void
17+
{
18+
}
19+
20+
#[LiveAction]
21+
public function cancel(): void
22+
{
23+
}
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule\Fixture;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
9+
#[AsLiveComponent]
10+
final class LiveComponentWithoutLiveAction
11+
{
12+
public string $count = '0';
13+
14+
public function increment(): void
15+
{
16+
}
17+
18+
private function helperMethod(): void
19+
{
20+
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule\Fixture;
6+
7+
class NotAComponent
8+
{
9+
private function save(): void
10+
{
11+
}
12+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule;
6+
7+
use Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
/**
12+
* @extends RuleTestCase<LiveActionMethodsShouldBePublicRule>
13+
*/
14+
final class LiveActionMethodsShouldBePublicRuleTest extends RuleTestCase
15+
{
16+
public function testViolations(): void
17+
{
18+
$this->analyse(
19+
[__DIR__ . '/Fixture/LiveComponentWithPrivateLiveAction.php'],
20+
[
21+
[
22+
'LiveAction method "save()" should be public.',
23+
15,
24+
'Methods annotated with #[LiveAction] must be public to be accessible as component actions.',
25+
],
26+
]
27+
);
28+
29+
$this->analyse(
30+
[__DIR__ . '/Fixture/LiveComponentWithProtectedLiveAction.php'],
31+
[
32+
[
33+
'LiveAction method "delete()" should be public.',
34+
15,
35+
'Methods annotated with #[LiveAction] must be public to be accessible as component actions.',
36+
],
37+
]
38+
);
39+
}
40+
41+
public function testNoViolations(): void
42+
{
43+
$this->analyse(
44+
[__DIR__ . '/Fixture/NotAComponent.php'],
45+
[]
46+
);
47+
48+
$this->analyse(
49+
[__DIR__ . '/Fixture/LiveComponentWithPublicLiveAction.php'],
50+
[]
51+
);
52+
53+
$this->analyse(
54+
[__DIR__ . '/Fixture/LiveComponentWithoutLiveAction.php'],
55+
[]
56+
);
57+
}
58+
59+
public static function getAdditionalConfigFiles(): array
60+
{
61+
return [__DIR__ . '/config/configured_rule.neon'];
62+
}
63+
64+
protected function getRule(): Rule
65+
{
66+
return self::getContainer()->getByType(LiveActionMethodsShouldBePublicRule::class);
67+
}
68+
}
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\LiveActionMethodsShouldBePublicRule

0 commit comments

Comments
 (0)