Skip to content

Commit

Permalink
[symfony] Add RequiredOnlyInAbstractRule (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba authored Jan 7, 2025
1 parent 2b10e91 commit 588c6c6
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 0 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,17 @@ class SomeClass extends SomeParentClass

<br>

### RequiredOnlyInAbstractRule

`@required` annotation should be used only in abstract classes, to child classes can use clean `__construct()` service injection.

```yaml
rules:
- Symplify\PHPStanRules\Rules\Symfony\RequiredOnlyInAbstractRule
```

<br>

### SingleRequiredMethodRule

There must be maximum 1 @required method in the class. Merge it to one to avoid possible injection collision or duplicated injects.
Expand Down
2 changes: 2 additions & 0 deletions src/Enum/SymfonyRuleIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ final class SymfonyRuleIdentifier
public const SYMFONY_NO_ABSTRACT_CONTROLLER_CONSTRUCTOR = 'symfony.noAbstractControllerConstructor';

public const SINGLE_REQUIRED_METHOD = 'symfony.singleRequiredMethod';

public const SYMFONY_REQUIRED_ONLY_IN_ABSTRACT = 'symfony.requiredOnlyInAbstract';
}
56 changes: 56 additions & 0 deletions src/Rules/Symfony/RequiredOnlyInAbstractRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Rules\Symfony;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use Symplify\PHPStanRules\Enum\SymfonyRuleIdentifier;
use Symplify\PHPStanRules\NodeAnalyzer\SymfonyRequiredMethodAnalyzer;

/**
* @see \Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\RequiredOnlyInAbstractRuleTest
*
* @implements Rule<Class_>
*/
final class RequiredOnlyInAbstractRule implements Rule
{
/**
* @var string
*/
public const ERROR_MESSAGE = '#@required is reserved exclusively for abstract classes. For the rest of classes, use clean constructor injection';

public function getNodeType(): string
{
return Class_::class;
}

/**
* @param Class_ $node
*/
public function processNode(Node $node, Scope $scope): array
{
foreach ($node->getMethods() as $classMethod) {
if (! SymfonyRequiredMethodAnalyzer::detect($classMethod)) {
continue;
}

if ($node->isAbstract()) {
continue;
}

$identifierRuleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
->line($classMethod->getLine())
->identifier(SymfonyRuleIdentifier::SYMFONY_REQUIRED_ONLY_IN_ABSTRACT)
->build();

return [$identifierRuleError];
}

return [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\Fixture;

final class NonAbstractControllerWithRequired
{
/**
* @required
*/
public function someMethod()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\Fixture;

abstract class SkipAbstractClass
{
/**
* @required
*/
public function someMethod()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Symfony\RequiredOnlyInAbstractRule;

final class RequiredOnlyInAbstractRuleTest extends RuleTestCase
{
#[DataProvider('provideData')]
public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
{
$this->analyse([$filePath], $expectedErrorMessagesWithLines);
}

public static function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/NonAbstractControllerWithRequired.php', [[
RequiredOnlyInAbstractRule::ERROR_MESSAGE,
12,
]]];

yield [__DIR__ . '/Fixture/SkipAbstractClass.php', []];
}

protected function getRule(): Rule
{
return new RequiredOnlyInAbstractRule();
}
}

0 comments on commit 588c6c6

Please sign in to comment.