Skip to content

Commit 5b92db7

Browse files
committed
Add NoClassConstFetchOnFactoriesFunctions
1 parent c19bd2b commit 5b92db7

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This extension provides the following features:
2424
* Disallows instantiating cache handlers using `new` and suggests to use the `CacheFactory` class instead.
2525
* Disallows instantiating `FrameworkException` classes using `new`.
2626
* Disallows direct re-assignment or access of `$_SERVER` and `$_GET` and suggests to use the `Superglobals` class instead.
27+
* Disallows use of `::class` fetch on `config()` and `model()` and suggests to use the short form of the class instead.
2728

2829
## Installation
2930

extension.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,6 @@ conditionalTags:
8888
rules:
8989
- CodeIgniter\PHPStan\Rules\Classes\CacheHandlerInstantiationRule
9090
- CodeIgniter\PHPStan\Rules\Classes\FrameworkExceptionInstantiationRule
91+
- CodeIgniter\PHPStan\Rules\Functions\NoClassConstFetchOnFactoriesFunctions
9192
- CodeIgniter\PHPStan\Rules\Superglobals\SuperglobalAccessRule
9293
- CodeIgniter\PHPStan\Rules\Superglobals\SuperglobalAssignRule
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\PHPStan\Rules\Functions;
15+
16+
use CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper;
17+
use PhpParser\Node;
18+
use PHPStan\Analyser\Scope;
19+
use PHPStan\Reflection\ReflectionProvider;
20+
use PHPStan\Rules\Rule;
21+
use PHPStan\Rules\RuleErrorBuilder;
22+
23+
/**
24+
* @implements Rule<Node\Expr\FuncCall>
25+
*/
26+
final class NoClassConstFetchOnFactoriesFunctions implements Rule
27+
{
28+
public function __construct(
29+
private readonly ReflectionProvider $reflectionProvider,
30+
private readonly FactoriesReturnTypeHelper $factoriesReturnTypeHelper
31+
) {}
32+
33+
public function getNodeType(): string
34+
{
35+
return Node\Expr\FuncCall::class;
36+
}
37+
38+
/**
39+
* @param Node\Expr\FuncCall $node
40+
*/
41+
public function processNode(Node $node, Scope $scope): array
42+
{
43+
if (! $node->name instanceof Node\Name) {
44+
return [];
45+
}
46+
47+
$nameNode = $node->name;
48+
$function = $this->reflectionProvider->resolveFunctionName($nameNode, $scope);
49+
50+
if (! in_array($function, ['config', 'model'], true)) {
51+
return [];
52+
}
53+
54+
$args = $node->getArgs();
55+
56+
if ($args === []) {
57+
return [];
58+
}
59+
60+
$classConstFetch = $args[0]->value;
61+
62+
if (! $classConstFetch instanceof Node\Expr\ClassConstFetch) {
63+
return [];
64+
}
65+
66+
if (! $classConstFetch->class instanceof Node\Name) {
67+
return [];
68+
}
69+
70+
if (! $classConstFetch->name instanceof Node\Identifier || $classConstFetch->name->name !== 'class') {
71+
return [];
72+
}
73+
74+
$returnType = $this->factoriesReturnTypeHelper->check($scope->getType($classConstFetch), $function);
75+
76+
if ($returnType->isNull()->yes()) {
77+
return [];
78+
}
79+
80+
$reflections = $returnType->getObjectClassReflections();
81+
82+
if ($reflections === []) {
83+
return [];
84+
}
85+
86+
return [
87+
RuleErrorBuilder::message(sprintf(
88+
'Call to function %s with %s::class is discouraged.',
89+
$function,
90+
$reflections[0]->getDisplayName()
91+
))->tip(sprintf(
92+
'Use %s(\'%s\') instead to allow overriding.',
93+
$function,
94+
$reflections[0]->getNativeReflection()->getShortName()
95+
))->identifier('codeigniter.factoriesClassConstFetch')->build(),
96+
];
97+
}
98+
}
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+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\PHPStan\Tests\Rules\Functions;
15+
16+
use CodeIgniter\PHPStan\Rules\Functions\NoClassConstFetchOnFactoriesFunctions;
17+
use CodeIgniter\PHPStan\Tests\AdditionalConfigFilesTrait;
18+
use CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper;
19+
use PHPStan\Rules\Rule;
20+
use PHPStan\Testing\RuleTestCase;
21+
use PHPUnit\Framework\Attributes\Group;
22+
23+
/**
24+
* @internal
25+
*
26+
* @extends RuleTestCase<NoClassConstFetchOnFactoriesFunctions>
27+
*/
28+
#[Group('Integration')]
29+
final class NoClassConstFetchOnFactoriesFunctionsTest extends RuleTestCase
30+
{
31+
use AdditionalConfigFilesTrait;
32+
33+
protected function getRule(): Rule
34+
{
35+
return new NoClassConstFetchOnFactoriesFunctions(
36+
self::createReflectionProvider(),
37+
self::getContainer()->getByType(FactoriesReturnTypeHelper::class)
38+
);
39+
}
40+
41+
public function testRule(): void
42+
{
43+
$this->analyse([
44+
__DIR__ . '/../../Fixtures/Type/config.php',
45+
__DIR__ . '/../../Fixtures/Type/model.php',
46+
], [
47+
[
48+
'Call to function config with Config\App::class is discouraged.',
49+
26,
50+
'Use config(\'App\') instead to allow overriding.',
51+
],
52+
[
53+
'Call to function model with stdClass::class is discouraged.',
54+
19,
55+
'Use model(\'stdClass\') instead to allow overriding.',
56+
],
57+
[
58+
'Call to function model with Closure::class is discouraged.',
59+
20,
60+
'Use model(\'Closure\') instead to allow overriding.',
61+
],
62+
]);
63+
}
64+
}

0 commit comments

Comments
 (0)