Skip to content

Commit c00be8e

Browse files
authored
Merge pull request #10 from Kocal/PostMountMethodSignatureRule
2 parents 62a9145 + 055bb6b commit c00be8e

34 files changed

+809
-200
lines changed

README.md

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -346,27 +346,30 @@ final class Alert
346346

347347
<br>
348348

349-
### PublicPropertiesShouldBeCamelCaseRule
349+
### PostMountMethodSignatureRule
350350

351-
Enforces that all public properties in Twig Components follow camelCase naming convention.
352-
This ensures consistency and better integration with Twig templates where properties are passed and accessed using camelCase.
351+
Enforces that methods with the `#[PostMount]` attribute have the correct signature: they must be public with an optional parameter of type `array`, and a return type of `array`, `void`, or `array|void`.
352+
This ensures proper integration with the Symfony UX TwigComponent lifecycle hooks.
353353

354354
```yaml
355355
rules:
356-
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PublicPropertiesShouldBeCamelCaseRule
356+
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PostMountMethodSignatureRule
357357
```
358358
359359
```php
360360
// src/Twig/Components/Alert.php
361361
namespace App\Twig\Components;
362362

363363
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
364+
use Symfony\UX\TwigComponent\Attribute\PostMount;
364365

365366
#[AsTwigComponent]
366367
final class Alert
367368
{
368-
public string $user_name;
369-
public bool $is_active;
369+
#[PostMount]
370+
protected function postMount(): void
371+
{
372+
}
370373
}
371374
```
372375

@@ -375,11 +378,33 @@ final class Alert
375378
namespace App\Twig\Components;
376379

377380
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
381+
use Symfony\UX\TwigComponent\Attribute\PostMount;
378382

379383
#[AsTwigComponent]
380384
final class Alert
381385
{
382-
public string $UserName;
386+
#[PostMount]
387+
public function postMount(array $data): string
388+
{
389+
return 'invalid';
390+
}
391+
}
392+
```
393+
394+
```php
395+
// src/Twig/Components/Alert.php
396+
namespace App\Twig\Components;
397+
398+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
399+
use Symfony\UX\TwigComponent\Attribute\PostMount;
400+
401+
#[AsTwigComponent]
402+
final class Alert
403+
{
404+
#[PostMount]
405+
public function postMount(string $data): void
406+
{
407+
}
383408
}
384409
```
385410

@@ -392,12 +417,33 @@ final class Alert
392417
namespace App\Twig\Components;
393418

394419
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
420+
use Symfony\UX\TwigComponent\Attribute\PostMount;
395421

396422
#[AsTwigComponent]
397423
final class Alert
398424
{
399-
public string $userName;
400-
public bool $isActive;
425+
#[PostMount]
426+
public function postMount(): void
427+
{
428+
}
429+
}
430+
```
431+
432+
```php
433+
// src/Twig/Components/Alert.php
434+
namespace App\Twig\Components;
435+
436+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
437+
use Symfony\UX\TwigComponent\Attribute\PostMount;
438+
439+
#[AsTwigComponent]
440+
final class Alert
441+
{
442+
#[PostMount]
443+
public function postMount(array $data): array
444+
{
445+
return $data;
446+
}
401447
}
402448
```
403449

@@ -407,7 +453,7 @@ final class Alert
407453

408454
### PreMountMethodSignatureRule
409455

410-
Enforces that methods with the `#[PreMount]` attribute have the correct signature: they must be public and have exactly one parameter of type `array`, with a return type of `array`.
456+
Enforces that methods with the `#[PreMount]` attribute have the correct signature: they must be public and have exactly one parameter of type `array`, with a return type of `array`, `void`, or `array|void` .
411457
This ensures proper integration with the Symfony UX TwigComponent lifecycle hooks.
412458

413459
```yaml
@@ -444,12 +490,17 @@ use Symfony\UX\TwigComponent\Attribute\PreMount;
444490
final class Alert
445491
{
446492
#[PreMount]
447-
public function preMount(array $data): void
493+
public function preMount(string $data): array
448494
{
495+
return [];
449496
}
450497
}
451498
```
452499

500+
:x:
501+
502+
<br>
503+
453504
```php
454505
// src/Twig/Components/Alert.php
455506
namespace App\Twig\Components;
@@ -461,34 +512,71 @@ use Symfony\UX\TwigComponent\Attribute\PreMount;
461512
final class Alert
462513
{
463514
#[PreMount]
464-
public function preMount(string $data): array
515+
public function preMount(array $data): array
465516
{
466-
return [];
517+
$data['timestamp'] = time();
518+
519+
return $data;
467520
}
468521
}
469522
```
470523

471-
:x:
524+
:+1:
472525

473526
<br>
474527

528+
### PublicPropertiesShouldBeCamelCaseRule
529+
530+
Enforces that all public properties in Twig Components follow camelCase naming convention.
531+
This ensures consistency and better integration with Twig templates where properties are passed and accessed using camelCase.
532+
533+
```yaml
534+
rules:
535+
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PublicPropertiesShouldBeCamelCaseRule
536+
```
537+
475538
```php
476539
// src/Twig/Components/Alert.php
477540
namespace App\Twig\Components;
478541

479542
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
480-
use Symfony\UX\TwigComponent\Attribute\PreMount;
481543

482544
#[AsTwigComponent]
483545
final class Alert
484546
{
485-
#[PreMount]
486-
public function preMount(array $data): array
487-
{
488-
$data['timestamp'] = time();
547+
public string $user_name;
548+
public bool $is_active;
549+
}
550+
```
489551

490-
return $data;
491-
}
552+
```php
553+
// src/Twig/Components/Alert.php
554+
namespace App\Twig\Components;
555+
556+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
557+
558+
#[AsTwigComponent]
559+
final class Alert
560+
{
561+
public string $UserName;
562+
}
563+
```
564+
565+
:x:
566+
567+
<br>
568+
569+
```php
570+
// src/Twig/Components/Alert.php
571+
namespace App\Twig\Components;
572+
573+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
574+
575+
#[AsTwigComponent]
576+
final class Alert
577+
{
578+
public string $userName;
579+
public bool $isActive;
492580
}
493581
```
494582

ecs.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
->withPreparedSets(psr12: true, common: true)
1515
->withSkip([
1616
ProtectedToPrivateFixer::class => [
17-
__DIR__ . '/tests/Rules/TwigComponent/MethodsShouldBePublicOrPrivateRule/Fixture/*',
17+
__DIR__ . '/tests/Rules/**/Fixture/*',
1818
],
1919
])
2020
;

phpstan.dist.neon

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,4 @@ parameters:
1212
identifiers:
1313
- method.unused
1414
- missingType.iterableValue
15-
- missingType.return
1615
- property.unused
17-
- return.unusedType
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Rules\TwigComponent;
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\Reflection\ExtendedMethodReflection;
12+
use PHPStan\Reflection\ReflectionProvider;
13+
use PHPStan\Rules\Rule;
14+
use PHPStan\Rules\RuleErrorBuilder;
15+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
16+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
17+
use Symfony\UX\TwigComponent\Attribute\PostMount;
18+
19+
/**
20+
* @implements Rule<Class_>
21+
*/
22+
final class PostMountMethodSignatureRule implements Rule
23+
{
24+
public function __construct(
25+
private ReflectionProvider $reflectionProvider,
26+
) {
27+
}
28+
29+
public function getNodeType(): string
30+
{
31+
return Class_::class;
32+
}
33+
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
if (! AttributeFinder::findAnyAttribute($node, [AsTwigComponent::class, AsLiveComponent::class])) {
37+
return [];
38+
}
39+
40+
if ($node->namespacedName === null) {
41+
return [];
42+
}
43+
44+
$errors = [];
45+
$reflClass = $this->reflectionProvider->getClass($node->namespacedName->toString());
46+
47+
foreach ($node->getMethods() as $method) {
48+
if (! AttributeFinder::findAttribute($method, PostMount::class)) {
49+
continue;
50+
}
51+
52+
$errors[] = $this->validatePostMountMethod(
53+
$method,
54+
$reflClass->getMethod($method->name->name, $scope),
55+
);
56+
}
57+
58+
return array_merge(...$errors);
59+
}
60+
61+
/**
62+
* @return list<\PHPStan\Rules\IdentifierRuleError>
63+
*/
64+
private function validatePostMountMethod(Node\Stmt\ClassMethod $method, ExtendedMethodReflection $reflMethod): array
65+
{
66+
$errors = [];
67+
68+
$methodName = $reflMethod->getName();
69+
$methodParams = $reflMethod->getOnlyVariant()->getParameters();
70+
$methodReturnType = $reflMethod->getOnlyVariant()->getReturnType();
71+
72+
// Check if method is public
73+
if (! $reflMethod->isPublic()) {
74+
$errors[] = RuleErrorBuilder::message(sprintf('Method "%s" with #[PostMount] attribute must be public.', $methodName))
75+
->identifier('symfonyUX.twigComponent.postMountPublic')
76+
->line($method->getLine())
77+
->tip('Change the method visibility to public.')
78+
->build();
79+
}
80+
81+
// Check parameter count and type (0 or 1 parameter allowed)
82+
if (count($methodParams) > 1) {
83+
$errors[] = RuleErrorBuilder::message(sprintf('Method "%s" with #[PostMount] attribute must have at most one parameter of type "array".', $methodName))
84+
->identifier('symfonyUX.twigComponent.postMountParameterCount')
85+
->line($method->getLine())
86+
->tip('The method should have zero or one parameter: "array $data" (optional).')
87+
->build();
88+
} elseif (count($methodParams) === 1) {
89+
// If there is a parameter, it must be of type array
90+
if (! $methodParams[0]->getType()->isArray()->yes()) {
91+
$errors[] = RuleErrorBuilder::message(sprintf('Method "%s" with #[PostMount] attribute must have a parameter of type "array".', $methodName))
92+
->identifier('symfonyUX.twigComponent.postMountParameterType')
93+
->line($method->getLine())
94+
->tip('Change the parameter type to "array".')
95+
->build();
96+
}
97+
}
98+
99+
// Check return type (must be array, void, or array|void)
100+
$isValidReturnType = $methodReturnType->isVoid()->yes()
101+
|| $methodReturnType->isArray()->yes()
102+
|| ($methodReturnType->isArray()->maybe() && $methodReturnType->isVoid()->maybe());
103+
104+
if (! $isValidReturnType) {
105+
$errors[] = RuleErrorBuilder::message(sprintf('Method "%s" with #[PostMount] attribute must have a return type of "array", "void", or "array|void".', $methodName))
106+
->identifier('symfonyUX.twigComponent.postMountReturnType')
107+
->line($method->getLine())
108+
->tip('Change the return type to ": array", ": void", or ": array|void".')
109+
->build();
110+
}
111+
112+
return $errors;
113+
}
114+
}

0 commit comments

Comments
 (0)