Skip to content

Commit 8edfa9f

Browse files
committed
InstantiationRule - call RestrictedMethodUsageExtension for constructor
1 parent d6446f9 commit 8edfa9f

File tree

6 files changed

+93
-1
lines changed

6 files changed

+93
-1
lines changed

src/Rules/Classes/InstantiationRule.php

+26
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node;
66
use PhpParser\Node\Expr\New_;
77
use PHPStan\Analyser\Scope;
8+
use PHPStan\DependencyInjection\Container;
89
use PHPStan\Internal\SprintfHelper;
910
use PHPStan\Reflection\ClassReflection;
1011
use PHPStan\Reflection\ParametersAcceptorSelector;
@@ -15,6 +16,8 @@
1516
use PHPStan\Rules\ClassNameUsageLocation;
1617
use PHPStan\Rules\FunctionCallParametersCheck;
1718
use PHPStan\Rules\IdentifierRuleError;
19+
use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension;
20+
use PHPStan\Rules\RestrictedUsage\RewrittenDeclaringClassMethodReflection;
1821
use PHPStan\Rules\Rule;
1922
use PHPStan\Rules\RuleErrorBuilder;
2023
use PHPStan\ShouldNotHappenException;
@@ -33,6 +36,7 @@ final class InstantiationRule implements Rule
3336
{
3437

3538
public function __construct(
39+
private Container $container,
3640
private ReflectionProvider $reflectionProvider,
3741
private FunctionCallParametersCheck $check,
3842
private ClassNameCheck $classCheck,
@@ -197,6 +201,28 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $
197201
->build();
198202
}
199203

204+
/** @var RestrictedMethodUsageExtension[] $restrictedUsageExtensions */
205+
$restrictedUsageExtensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG);
206+
207+
foreach ($restrictedUsageExtensions as $extension) {
208+
$restrictedUsage = $extension->isRestrictedMethodUsage($constructorReflection, $scope);
209+
if ($restrictedUsage === null) {
210+
continue;
211+
}
212+
213+
if ($classReflection->getName() !== $constructorReflection->getDeclaringClass()->getName()) {
214+
$rewrittenConstructorReflection = new RewrittenDeclaringClassMethodReflection($classReflection, $constructorReflection);
215+
$rewrittenRestrictedUsage = $extension->isRestrictedMethodUsage($rewrittenConstructorReflection, $scope);
216+
if ($rewrittenRestrictedUsage === null) {
217+
continue;
218+
}
219+
}
220+
221+
$messages[] = RuleErrorBuilder::message($restrictedUsage->errorMessage)
222+
->identifier($restrictedUsage->identifier)
223+
->build();
224+
}
225+
200226
$classDisplayName = SprintfHelper::escapeFormatString($classReflection->getDisplayName());
201227

202228
return array_merge($messages, $this->check->check(

tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ protected function getRule(): Rule
2424
{
2525
$reflectionProvider = $this->createReflectionProvider();
2626
return new InstantiationRule(
27+
self::getContainer(),
2728
$reflectionProvider,
2829
new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true),
2930
new ClassNameCheck(

tests/PHPStan/Rules/Classes/InstantiationRuleTest.php

+30
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ protected function getRule(): Rule
2424
{
2525
$reflectionProvider = $this->createReflectionProvider();
2626
return new InstantiationRule(
27+
self::getContainer(),
2728
$reflectionProvider,
2829
new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true),
2930
new ClassNameCheck(
@@ -556,4 +557,33 @@ public function testClassString(): void
556557
]);
557558
}
558559

560+
public function testInternalConstructor(): void
561+
{
562+
$this->analyse([__DIR__ . '/data/internal-constructor.php'], [
563+
[
564+
'Call to internal method InternalConstructorDefinition\Foo::__construct() from outside its root namespace InternalConstructorDefinition.',
565+
21,
566+
],
567+
]);
568+
}
569+
570+
public function testBug12951(): void
571+
{
572+
if (PHP_VERSION_ID < 80100) {
573+
self::markTestSkipped('Test requires PHP 8.1');
574+
}
575+
576+
require_once __DIR__ . '/../InternalTag/data/bug-12951-define.php';
577+
$this->analyse([__DIR__ . '/../InternalTag/data/bug-12951-constructor.php'], [
578+
[
579+
'Instantiation of internal class Bug12951Polyfill\NumberFormatter.',
580+
7,
581+
],
582+
[
583+
'Call to method __construct() of internal class Bug12951Polyfill\NumberFormatter from outside its root namespace Bug12951Polyfill.',
584+
7,
585+
],
586+
]);
587+
}
588+
559589
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace InternalConstructorDefinition {
4+
class Foo
5+
{
6+
7+
/**
8+
* @internal
9+
*/
10+
public function __construct()
11+
{
12+
13+
}
14+
15+
}
16+
17+
$a = new Foo();
18+
}
19+
20+
namespace InternalConstructorUsage {
21+
$a = new \InternalConstructorDefinition\Foo();
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug12951;
4+
5+
function (): void {
6+
new \Bug12951Core\NumberFormatter();
7+
new \Bug12951Polyfill\NumberFormatter();
8+
};

tests/PHPStan/Rules/InternalTag/data/bug-12951-define.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ class NumberFormatter extends \Bug12951Polyfill\NumberFormatter
1212
namespace Bug12951Polyfill {
1313

1414
/** @internal */
15-
abstract class NumberFormatter
15+
class NumberFormatter
1616
{
1717

1818
public const NUMERIC_COLLATION = 1;
1919

2020
public static $prop;
2121

22+
public function __construct()
23+
{
24+
25+
}
26+
2227
public static function doBar(): void
2328
{
2429

0 commit comments

Comments
 (0)