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
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,68 @@ final class TodoList

<br>

### LiveListenerMethodsShouldBePublicRule

Enforces that all methods annotated with `#[LiveListener]` in LiveComponents must be declared as public.
LiveListener methods need to be publicly accessible to be invoked when listening to events from the frontend.

```yaml
rules:
- Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule
```

```php
// src/Twig/Components/Notification.php
namespace App\Twig\Components;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveListener;

#[AsLiveComponent]
final class Notification
{
#[LiveListener('notification:received')]
private function onNotificationReceived(): void
{
}

#[LiveListener('notification:dismissed')]
protected function onNotificationDismissed(): void
{
}
}
```

:x:

<br>

```php
// src/Twig/Components/Notification.php
namespace App\Twig\Components;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveListener;

#[AsLiveComponent]
final class Notification
{
#[LiveListener('notification:received')]
public function onNotificationReceived(): void
{
}

#[LiveListener('notification:dismissed')]
public function onNotificationDismissed(): void
{
}
}
```

:+1:

<br>

## TwigComponent Rules

> [!NOTE]
Expand Down
53 changes: 53 additions & 0 deletions src/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Rules\LiveComponent;

use Kocal\PHPStanSymfonyUX\NodeAnalyzer\AttributeFinder;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveListener;

/**
* @implements Rule<Class_>
*/
final class LiveListenerMethodsShouldBePublicRule implements Rule
{
public function getNodeType(): string
{
return Class_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (! AttributeFinder::findAnyAttribute($node, [AsLiveComponent::class])) {
return [];
}

$errors = [];

foreach ($node->getMethods() as $method) {
if (! AttributeFinder::findAnyAttribute($method, [LiveListener::class])) {
continue;
}

if (! $method->isPublic()) {
$errors[] = RuleErrorBuilder::message(sprintf(
'LiveListener method "%s()" should be public.',
$method->name->toString()
))
->identifier('symfonyUX.liveComponent.liveListenerMethodShouldBePublic')
->line($method->getLine())
->tip('Change the method visibility to public.')
->build();
}
}

return $errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;

#[AsLiveComponent]
final class NoLiveListener
{
protected function someProtectedMethod(): void
{
}

private function somePrivateMethod(): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;

use Symfony\UX\LiveComponent\Attribute\LiveListener;

final class NotAComponent
{
#[LiveListener('some.event')]
private function onSomeEvent(): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveListener;

#[AsLiveComponent]
final class PrivateLiveListener
{
#[LiveListener('another.event')]
protected function onAnotherEvent(): void
{
}

#[LiveListener('some.event')]
private function onSomeEvent(): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule\Fixture;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveListener;

#[AsLiveComponent]
final class PublicLiveListener
{
#[LiveListener('some.event')]
public function onSomeEvent(): void
{
}

#[LiveListener('another.event')]
public function onAnotherEvent(): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule;

use Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<LiveListenerMethodsShouldBePublicRule>
*/
final class LiveListenerMethodsShouldBePublicRuleTest extends RuleTestCase
{
public function testViolations(): void
{
$this->analyse(
[__DIR__ . '/Fixture/PrivateLiveListener.php'],
[
[
'LiveListener method "onAnotherEvent()" should be public.',
13,
'Change the method visibility to public.',
],
[
'LiveListener method "onSomeEvent()" should be public.',
18,
'Change the method visibility to public.',
],
]
);
}

public function testNoViolations(): void
{
$this->analyse(
[__DIR__ . '/Fixture/NotAComponent.php'],
[]
);

$this->analyse(
[__DIR__ . '/Fixture/PublicLiveListener.php'],
[]
);

$this->analyse(
[__DIR__ . '/Fixture/NoLiveListener.php'],
[]
);
}

public static function getAdditionalConfigFiles(): array
{
return [__DIR__ . '/config/configured_rule.neon'];
}

protected function getRule(): Rule
{
return self::getContainer()->getByType(LiveListenerMethodsShouldBePublicRule::class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rules:
- Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule