Skip to content

Kocal/phpstan-symfony-ux

PHPStan for Symfony UX

Packagist License Packagist Version

A set of PHPStan rules to improve static analysis for Symfony UX applications.

Installation

To install the PHPStan rules for Symfony UX, you can use Composer:

composer require --dev kocal/phpstan-symfony-ux

If you have phpstan/extension-installer installed (which is the case by default), the extension will be automatically registered and you're ready to go.

If you don't use the extension installer, you'll need to manually add the extension to your phpstan.neon or phpstan.dist.neon configuration file:

includes:
    - vendor/kocal/phpstan-symfony-ux/extension.neon

Configuration

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

LiveComponent Rules

LiveActionMethodsShouldBePublicRule

Enforces that all methods annotated with #[LiveAction] in LiveComponents must be declared as public. LiveAction methods need to be publicly accessible to be invoked as component actions from the frontend.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveActionMethodsShouldBePublicRule
// src/Twig/Components/TodoList.php
namespace App\Twig\Components;

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

#[AsLiveComponent]
final class TodoList
{
    #[LiveAction]
    private function addItem(): void
    {
    }
}
// src/Twig/Components/TodoList.php
namespace App\Twig\Components;

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

#[AsLiveComponent]
final class TodoList
{
    #[LiveAction]
    protected function deleteItem(): void
    {
    }
}

❌


// src/Twig/Components/TodoList.php
namespace App\Twig\Components;

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

#[AsLiveComponent]
final class TodoList
{
    #[LiveAction]
    public function addItem(): void
    {
    }

    #[LiveAction]
    public function deleteItem(): void
    {
    }
}

πŸ‘


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.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule
// 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
    {
    }
}

❌


// 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
    {
    }
}

πŸ‘


TwigComponent Rules

Note

All these rules also apply to LiveComponents (classes annotated with #[AsLiveComponent]).

ClassMustBeFinalRule

Enforces that all Twig Component classes must be declared as final. This prevents inheritance and promotes composition via traits, ensuring better code maintainability and avoiding tight coupling between components.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ClassMustBeFinalRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Alert
{
    public string $message;
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
abstract class Alert
{
    public string $message;
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $message;
}

πŸ‘


ClassNameShouldNotEndWithComponentRule

Forbid Twig Component class names from ending with "Component" suffix, as it creates redundancy since the class is already identified as a component through the #[AsTwigComponent] attribute.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ClassNameShouldNotEndWithComponentRule
// src/Twig/Components/AlertComponent.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class AlertComponent
{
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
}

πŸ‘


ExposePublicPropsShouldBeFalseRule

Enforces that the #[AsTwigComponent] attribute has its exposePublicProps parameter explicitly set to false. This prevents public properties from being automatically exposed to templates, promoting explicit control over what data is accessible in your Twig components.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $message;
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(exposePublicProps: true)]
final class Alert
{
    public string $message;
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(exposePublicProps: false)]
final class Alert
{
    public string $message;
}

πŸ‘


ForbiddenAttributesPropertyRule

Forbid the use of the $attributes property in Twig Components, which can lead to confusion when using {{ attributes }} (an instance of ComponentAttributes that is automatically injected) in Twig templates.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenAttributesPropertyRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public $attributes;
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(attributesVar: 'customAttributes')]
final class Alert
{
    public $customAttributes;
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public $customAttributes;
}

πŸ‘


ForbiddenClassPropertyRule

Forbid the use of the $class property in Twig Components, as it is considered a bad practice to manipulate CSS classes directly in components. Use {{ attributes }} or {{ attributes.defaults({ class: '...' }) }} in your Twig templates instead.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenClassPropertyRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public $class;
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
}

πŸ‘


MethodsShouldBePublicOrPrivateRule

Enforces that all methods in Twig Components are either public or private, but not protected. Since Twig Components must be final classes and inheritance is forbidden (see ForbiddenInheritanceRule), protected methods serve no purpose and should be avoided.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\MethodsShouldBePublicOrPrivateRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $message;

    protected function formatMessage(): string
    {
        return strtoupper($this->message);
    }
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $message;

    public function formatMessage(): string
    {
        return strtoupper($this->message);
    }

    private function helperMethod(): void
    {
        // ...
    }
}

πŸ‘


PostMountMethodSignatureRule

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. This ensures proper integration with the Symfony UX TwigComponent lifecycle hooks.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PostMountMethodSignatureRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsTwigComponent]
final class Alert
{
    #[PostMount]
    protected function postMount(): void
    {
    }
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsTwigComponent]
final class Alert
{
    #[PostMount]
    public function postMount(array $data): string
    {
        return 'invalid';
    }
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsTwigComponent]
final class Alert
{
    #[PostMount]
    public function postMount(string $data): void
    {
    }
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsTwigComponent]
final class Alert
{
    #[PostMount]
    public function postMount(): void
    {
    }
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsTwigComponent]
final class Alert
{
    #[PostMount]
    public function postMount(array $data): array
    {
        return $data;
    }
}

πŸ‘


PreMountMethodSignatureRule

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 . This ensures proper integration with the Symfony UX TwigComponent lifecycle hooks.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PreMountMethodSignatureRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PreMount;

#[AsTwigComponent]
final class Alert
{
    #[PreMount]
    protected function preMount(array $data): array
    {
        return $data;
    }
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PreMount;

#[AsTwigComponent]
final class Alert
{
    #[PreMount]
    public function preMount(string $data): array
    {
        return [];
    }
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\PreMount;

#[AsTwigComponent]
final class Alert
{
    #[PreMount]
    public function preMount(array $data): array
    {
        $data['timestamp'] = time();

        return $data;
    }
}

πŸ‘


PublicPropertiesShouldBeCamelCaseRule

Enforces that all public properties in Twig Components follow camelCase naming convention. This ensures consistency and better integration with Twig templates where properties are passed and accessed using camelCase.

rules:
    - Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PublicPropertiesShouldBeCamelCaseRule
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $user_name;
    public bool $is_active;
}
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $UserName;
}

❌


// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $userName;
    public bool $isActive;
}

πŸ‘


About

PHPStan rules for Symfony UX

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Sponsor this project

  •  

Languages