Skip to content

Commit ce257d9

Browse files
committed
Call RestrictedMethodUsageExtension for __toString methods in (string) cast
1 parent 8edfa9f commit ce257d9

File tree

5 files changed

+143
-2
lines changed

5 files changed

+143
-2
lines changed

conf/config.neon

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ rules:
227227
- PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule
228228
- PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodCallableUsageRule
229229
- PHPStan\Rules\RestrictedUsage\RestrictedStaticPropertyUsageRule
230+
- PHPStan\Rules\RestrictedUsage\RestrictedUsageOfDeprecatedStringCastRule
230231

231232
conditionalTags:
232233
PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\RestrictedUsage;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\Cast;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\DependencyInjection\Container;
9+
use PHPStan\Reflection\ReflectionProvider;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
13+
/**
14+
* @implements Rule<Cast\String_>
15+
*/
16+
final class RestrictedUsageOfDeprecatedStringCastRule implements Rule
17+
{
18+
19+
public function __construct(
20+
private Container $container,
21+
private ReflectionProvider $reflectionProvider,
22+
)
23+
{
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return Cast\String_::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
/** @var RestrictedMethodUsageExtension[] $extensions */
34+
$extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG);
35+
if ($extensions === []) {
36+
return [];
37+
}
38+
39+
$exprType = $scope->getType($node->expr);
40+
$referencedClasses = $exprType->getObjectClassNames();
41+
42+
$errors = [];
43+
44+
foreach ($referencedClasses as $referencedClass) {
45+
if (!$this->reflectionProvider->hasClass($referencedClass)) {
46+
continue;
47+
}
48+
49+
$classReflection = $this->reflectionProvider->getClass($referencedClass);
50+
if (!$classReflection->hasNativeMethod('__toString')) {
51+
continue;
52+
}
53+
54+
$methodReflection = $classReflection->getNativeMethod('__toString');
55+
foreach ($extensions as $extension) {
56+
$restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope);
57+
if ($restrictedUsage === null) {
58+
continue;
59+
}
60+
61+
$errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage)
62+
->identifier($restrictedUsage->identifier)
63+
->build();
64+
}
65+
}
66+
67+
return $errors;
68+
}
69+
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\RestrictedUsage;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<RestrictedUsageOfDeprecatedStringCastRule>
10+
*/
11+
class RestrictedUsageOfDeprecatedStringCastRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new RestrictedUsageOfDeprecatedStringCastRule(
17+
self::getContainer(),
18+
$this->createReflectionProvider(),
19+
);
20+
}
21+
22+
public function testRule(): void
23+
{
24+
$this->analyse([__DIR__ . '/data/restricted-to-string.php'], [
25+
[
26+
'Cannot call __toString',
27+
11,
28+
],
29+
]);
30+
}
31+
32+
public static function getAdditionalConfigFiles(): array
33+
{
34+
return [
35+
__DIR__ . '/restricted-usage.neon',
36+
...parent::getAdditionalConfigFiles(),
37+
];
38+
}
39+
40+
}

tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Reflection\ExtendedMethodReflection;
77
use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension;
88
use PHPStan\Rules\RestrictedUsage\RestrictedUsage;
9+
use function sprintf;
910

1011
class MethodExtension implements RestrictedMethodUsageExtension
1112
{
@@ -15,11 +16,11 @@ public function isRestrictedMethodUsage(
1516
Scope $scope
1617
): ?RestrictedUsage
1718
{
18-
if ($methodReflection->getName() !== 'doFoo') {
19+
if ($methodReflection->getName() !== 'doFoo' && $methodReflection->getName() !== '__toString') {
1920
return null;
2021
}
2122

22-
return RestrictedUsage::create('Cannot call doFoo', 'restrictedUsage.doFoo');
23+
return RestrictedUsage::create(sprintf('Cannot call %s', $methodReflection->getName()), 'restrictedUsage.doFoo');
2324
}
2425

2526
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace RestrictedUsage;
4+
5+
class FooToString
6+
{
7+
8+
public function doTest(Nonexistent $c): void
9+
{
10+
(string) $c;
11+
(string) $this;
12+
}
13+
14+
public function __toString()
15+
{
16+
return 'foo';
17+
}
18+
19+
public function doTest2(FooWithoutToString $f)
20+
{
21+
(string) $f;
22+
}
23+
24+
}
25+
26+
class FooWithoutToString
27+
{
28+
29+
}

0 commit comments

Comments
 (0)