Skip to content

Commit dac84b8

Browse files
authored
[symfony] Add RouteGenerateControllerClassRequireNameRule (#180)
* [symfony] Add RouteGenerateControllerClassRequireNameRule * move symfony classes ot own enum SymfonyClass
1 parent b8ad970 commit dac84b8

28 files changed

+475
-73
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,17 @@ rules:
875875

876876
<br>
877877

878+
### RequireRouteNameToGenerateControllerRouteRule
879+
880+
To pass a controller class to generate() method, the controller must have "#[Route(name: self::class)]" above the __invoke() method
881+
882+
```yaml
883+
rules:
884+
- Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule
885+
```
886+
887+
<br>
888+
878889
### SingleRequiredMethodRule
879890

880891
There must be maximum 1 @required method in the class. Merge it to one to avoid possible injection collision or duplicated injects.

config/symfony-rules.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ rules:
1111
- Symplify\PHPStanRules\Rules\Symfony\NoRoutingPrefixRule
1212
- Symplify\PHPStanRules\Rules\Symfony\NoClassLevelRouteRule
1313
- Symplify\PHPStanRules\Rules\Symfony\NoRouteTrailingSlashPathRule
14+
- Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule
1415

1516
# dependency injection
1617
- Symplify\PHPStanRules\Rules\Symfony\NoGetDoctrineInControllerRule

phpstan.neon

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ parameters:
99
level: 8
1010

1111
# custom configuration
12-
maximumIgnoredErrorCount: 10
12+
maximumIgnoredErrorCount: 12
1313

1414
paths:
1515
- src
@@ -49,3 +49,6 @@ parameters:
4949

5050
- '#Although PHPStan\\Node\\InClassNode is covered by backward compatibility promise, this instanceof assumption might break because (.*?) not guaranteed to always stay the same#'
5151
- '#PHPStan\\DependencyInjection\\NeonAdapter#'
52+
53+
# not useful
54+
- '#with generic class ReflectionAttribute (but )?does not specify its types#'

src/Enum/ClassName.php

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,11 @@
66

77
final class ClassName
88
{
9-
/**
10-
* @var string
11-
*/
12-
public const ROUTE_ATTRIBUTE = 'Symfony\Component\Routing\Attribute\Route';
13-
14-
/**
15-
* @api
16-
* @var string
17-
*/
18-
public const ROUTE_ANNOTATION = 'Symfony\Component\Routing\Annotation\Route';
19-
20-
/**
21-
* @var string
22-
*/
23-
public const SYMFONY_ABSTRACT_CONTROLLER = 'Symfony\Bundle\FrameworkBundle\Controller\AbstractController';
24-
259
/**
2610
* @var string
2711
*/
2812
public const PHPUNIT_TEST_CASE = 'PHPUnit\Framework\TestCase';
2913

30-
/**
31-
* @var string
32-
*/
33-
public const EVENT_DISPATCHER_INTERFACE = 'Symfony\Component\EventDispatcher\EventDispatcherInterface';
34-
35-
/**
36-
* @var string
37-
*/
38-
public const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
39-
4014
/**
4115
* @var string
4216
*/
@@ -62,21 +36,6 @@ final class ClassName
6236
*/
6337
public const RECTOR_ATTRIBUTE_KEY = 'Rector\NodeTypeResolver\Node\AttributeKey';
6438

65-
/**
66-
* @var string
67-
*/
68-
public const FORM_EVENTS = 'Symfony\Component\Form\FormEvents';
69-
70-
/**
71-
* @var string
72-
*/
73-
public const FORM_TYPE = 'Symfony\Component\Form\AbstractType';
74-
75-
/**
76-
* @var string
77-
*/
78-
public const SYMFONY_CONTROLLER = 'Symfony\Bundle\FrameworkBundle\Controller\Controller';
79-
8039
/**
8140
* @var string
8241
*/
@@ -91,14 +50,4 @@ final class ClassName
9150
* @var string
9251
*/
9352
public const MOCK_OBJECT_CLASS = 'PHPUnit\Framework\MockObject\MockObject';
94-
95-
/**
96-
* @var string
97-
*/
98-
public const REQUIRED_ATTRIBUTE = 'Symfony\Contracts\Service\Attribute\Required';
99-
100-
/**
101-
* @var string
102-
*/
103-
public const SYMFONY_ROUTE_IMPORT_CONFIGURATOR = 'Symfony\Component\Routing\Loader\Configurator\ImportConfigurator';
10453
}

src/Enum/SymfonyClass.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Enum;
6+
7+
final class SymfonyClass
8+
{
9+
/**
10+
* @var string
11+
*/
12+
public const ROUTE_ATTRIBUTE = 'Symfony\Component\Routing\Attribute\Route';
13+
14+
/**
15+
* @api
16+
* @var string
17+
*/
18+
public const ROUTE_ANNOTATION = 'Symfony\Component\Routing\Annotation\Route';
19+
20+
/**
21+
* @var string
22+
*/
23+
public const SYMFONY_CONTROLLER = 'Symfony\Bundle\FrameworkBundle\Controller\Controller';
24+
25+
/**
26+
* @var string
27+
*/
28+
public const REQUIRED_ATTRIBUTE = 'Symfony\Contracts\Service\Attribute\Required';
29+
30+
/**
31+
* @var string
32+
*/
33+
public const SYMFONY_ABSTRACT_CONTROLLER = 'Symfony\Bundle\FrameworkBundle\Controller\AbstractController';
34+
35+
/**
36+
* @var string
37+
*/
38+
public const EVENT_SUBSCRIBER_INTERFACE = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
39+
40+
/**
41+
* @var string
42+
*/
43+
public const EVENT_DISPATCHER_INTERFACE = 'Symfony\Component\EventDispatcher\EventDispatcherInterface';
44+
45+
/**
46+
* @var string
47+
*/
48+
public const FORM_TYPE = 'Symfony\Component\Form\AbstractType';
49+
50+
/**
51+
* @var string
52+
*/
53+
public const ROUTE_IMPORT_CONFIGURATOR = 'Symfony\Component\Routing\Loader\Configurator\ImportConfigurator';
54+
55+
/**
56+
* @var string
57+
*/
58+
public const FORM_EVENTS = 'Symfony\Component\Form\FormEvents';
59+
60+
/**
61+
* @var string
62+
*/
63+
public const URL_GENERATOR = 'Symfony\Component\Routing\Generator\UrlGeneratorInterface';
64+
}

src/Enum/SymfonyRuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,6 @@ final class SymfonyRuleIdentifier
3737
public const NO_ROUTE_TRAILING_SLASH_PATH = 'symfony.noRouteTrailingSlashPath';
3838

3939
public const NO_FIND_TAGGED_SERVICE_IDS_CALL = 'symfony.noFindTaggedServiceIdsCall';
40+
41+
public const REQUIRE_ROUTE_NAME_TO_GENERATE_CONTROLLER_ROUTE = 'symfony.requireRouteNameToGenerateControllerRoute';
4042
}

src/NodeAnalyzer/SymfonyRequiredMethodAnalyzer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use PhpParser\Comment\Doc;
88
use PhpParser\Node\Stmt\ClassMethod;
9-
use Symplify\PHPStanRules\Enum\ClassName;
9+
use Symplify\PHPStanRules\Enum\SymfonyClass;
1010

1111
final class SymfonyRequiredMethodAnalyzer
1212
{
@@ -23,7 +23,7 @@ public static function detect(ClassMethod $classMethod): bool
2323

2424
foreach ($classMethod->getAttrGroups() as $attributeGroup) {
2525
foreach ($attributeGroup->attrs as $attr) {
26-
if ($attr->name->toString() === ClassName::REQUIRED_ATTRIBUTE) {
26+
if ($attr->name->toString() === SymfonyClass::REQUIRED_ATTRIBUTE) {
2727
return true;
2828
}
2929
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Reflection;
4+
5+
use PHPStan\Reflection\ClassReflection;
6+
use ReflectionMethod;
7+
8+
final class InvokeClassMethodResolver
9+
{
10+
public static function resolve(ClassReflection $controllerClassReflection): null|ReflectionMethod
11+
{
12+
if (! $controllerClassReflection->hasMethod('__invoke')) {
13+
return null;
14+
}
15+
16+
$nativeReflectionClass = $controllerClassReflection->getNativeReflection();
17+
return $nativeReflectionClass->getMethod('__invoke');
18+
}
19+
}

src/Rules/ClassNameRespectsParentSuffixRule.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHPStan\Rules\RuleErrorBuilder;
1616
use Symplify\PHPStanRules\Enum\ClassName;
1717
use Symplify\PHPStanRules\Enum\RuleIdentifier;
18+
use Symplify\PHPStanRules\Enum\SymfonyClass;
1819
use Symplify\PHPStanRules\Naming\ClassToSuffixResolver;
1920

2021
/**
@@ -33,8 +34,8 @@ final class ClassNameRespectsParentSuffixRule implements Rule
3334
*/
3435
private const DEFAULT_PARENT_CLASSES = [
3536
'Symfony\Component\Console\Command\Command',
36-
ClassName::EVENT_SUBSCRIBER_INTERFACE,
37-
ClassName::SYMFONY_ABSTRACT_CONTROLLER,
37+
SymfonyClass::EVENT_SUBSCRIBER_INTERFACE,
38+
SymfonyClass::SYMFONY_ABSTRACT_CONTROLLER,
3839
ClassName::SNIFF,
3940
ClassName::PHPUNIT_TEST_CASE,
4041
Exception::class,

src/Rules/Symfony/FormTypeClassNameRule.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use PHPStan\Rules\RuleError;
1313
use PHPStan\Rules\RuleErrorBuilder;
1414
use PHPStan\Type\ObjectType;
15-
use Symplify\PHPStanRules\Enum\ClassName;
15+
use Symplify\PHPStanRules\Enum\SymfonyClass;
1616
use Symplify\PHPStanRules\Enum\SymfonyRuleIdentifier;
1717

1818
/**
@@ -45,14 +45,14 @@ public function processNode(Node $node, Scope $scope): array
4545

4646
$currentObjectType = new ObjectType($className);
4747

48-
$parentObjectType = new ObjectType(ClassName::FORM_TYPE);
48+
$parentObjectType = new ObjectType(SymfonyClass::FORM_TYPE);
4949
if (! $parentObjectType->isSuperTypeOf($currentObjectType)->yes()) {
5050
return [];
5151
}
5252

5353
$errorMessage = sprintf(
5454
'Class extends "%s" must have "FormType" suffix to make form explicit, "%s" given',
55-
ClassName::FORM_TYPE,
55+
SymfonyClass::FORM_TYPE,
5656
$className
5757
);
5858

0 commit comments

Comments
 (0)