Skip to content

Commit f51ff1a

Browse files
authored
Merge pull request #287 from mglaman/gh272-access-return-type-ext
Add return type extensions for AccessResult methods
2 parents 1a16c96 + 38d7a74 commit f51ff1a

8 files changed

+216
-2
lines changed

extension.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ services:
272272
-
273273
class: mglaman\PHPStanDrupal\Type\UrlToStringDynamicReturnTypeExtension
274274
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
275+
-
276+
class: mglaman\PHPStanDrupal\Type\AccessibleReturnTypeExtension
277+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
278+
-
279+
class: mglaman\PHPStanDrupal\Type\EntityAccessControlHandlerReturnTypeExtension
280+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
275281
-
276282
class: mglaman\PHPStanDrupal\Type\DrupalClassResolverDynamicStaticReturnTypeExtension
277283
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type;
4+
5+
use Drupal\Core\Access\AccessibleInterface;
6+
use Drupal\Core\Access\AccessResultInterface;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParametersAcceptorSelector;
11+
use PHPStan\Type\BooleanType;
12+
use PHPStan\Type\Constant\ConstantBooleanType;
13+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\Type;
16+
17+
final class AccessibleReturnTypeExtension implements DynamicMethodReturnTypeExtension
18+
{
19+
20+
public function getClass(): string
21+
{
22+
return AccessibleInterface::class;
23+
}
24+
25+
public function isMethodSupported(MethodReflection $methodReflection): bool
26+
{
27+
return $methodReflection->getName() === 'access';
28+
}
29+
30+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
31+
{
32+
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
33+
if (\count($methodCall->getArgs()) < 3) {
34+
return new BooleanType();
35+
}
36+
$returnAsObjectArg = $scope->getType($methodCall->getArgs()[2]->value);
37+
if (!$returnAsObjectArg instanceof ConstantBooleanType) {
38+
return $returnType;
39+
}
40+
41+
return $returnAsObjectArg->getValue() ? new ObjectType(AccessResultInterface::class) : new BooleanType();
42+
}
43+
}

src/Type/ContainerDynamicReturnTypeExtension.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
use PHPStan\ShouldNotHappenException;
1414
use PHPStan\Type\Constant\ConstantBooleanType;
1515
use PHPStan\Type\DynamicMethodReturnTypeExtension;
16-
use PHPStan\Type\ObjectType;
17-
use PHPStan\Type\StringType;
1816
use Psr\Container\ContainerInterface;
1917

2018
class ContainerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type;
4+
5+
use Drupal\Core\Access\AccessibleInterface;
6+
use Drupal\Core\Access\AccessResultInterface;
7+
use Drupal\Core\Entity\EntityAccessControlHandlerInterface;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Reflection\MethodReflection;
12+
use PHPStan\Reflection\ParametersAcceptorSelector;
13+
use PHPStan\Type\BooleanType;
14+
use PHPStan\Type\Constant\ConstantBooleanType;
15+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
16+
use PHPStan\Type\ObjectType;
17+
use PHPStan\Type\Type;
18+
19+
final class EntityAccessControlHandlerReturnTypeExtension implements DynamicMethodReturnTypeExtension
20+
{
21+
22+
public function getClass(): string
23+
{
24+
return EntityAccessControlHandlerInterface::class;
25+
}
26+
27+
public function isMethodSupported(MethodReflection $methodReflection): bool
28+
{
29+
return in_array($methodReflection->getName(), ['access', 'createAccess', 'fieldAccess'], true);
30+
}
31+
32+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
33+
{
34+
$returnType = new BooleanType();
35+
36+
$args = $methodCall->getArgs();
37+
$arg = null;
38+
if ($methodReflection->getName() === 'access' && \count($args) === 4) {
39+
$arg = $args[3];
40+
}
41+
if ($methodReflection->getName() === 'createAccess' && \count($args) === 4) {
42+
$arg = $args[3];
43+
}
44+
if ($methodReflection->getName() === 'fieldAccess' && \count($args) === 5) {
45+
$arg = $args[4];
46+
}
47+
48+
if ($arg === null) {
49+
return $returnType;
50+
}
51+
52+
$returnAsObjectArg = $scope->getType($arg->value);
53+
if (!$returnAsObjectArg instanceof ConstantBooleanType) {
54+
return $returnType;
55+
}
56+
return $returnAsObjectArg->getValue() ? new ObjectType(AccessResultInterface::class) : new BooleanType();
57+
}
58+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Tests\Type;
4+
5+
use mglaman\PHPStanDrupal\Tests\AdditionalConfigFilesTrait;
6+
use PHPStan\Testing\TypeInferenceTestCase;
7+
8+
final class AccessibleReturnTypeExtensionTest extends TypeInferenceTestCase {
9+
10+
use AdditionalConfigFilesTrait;
11+
12+
public function dataFileAsserts(): iterable
13+
{
14+
yield from $this->gatherAssertTypes(__DIR__ . '/data/accessible.php');
15+
}
16+
17+
/**
18+
* @dataProvider dataFileAsserts
19+
* @param string $assertType
20+
* @param string $file
21+
* @param mixed ...$args
22+
*/
23+
public function testFileAsserts(
24+
string $assertType,
25+
string $file,
26+
...$args
27+
): void
28+
{
29+
$this->assertFileAsserts($assertType, $file, ...$args);
30+
}
31+
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Tests\Type;
4+
5+
use mglaman\PHPStanDrupal\Tests\AdditionalConfigFilesTrait;
6+
use PHPStan\Testing\TypeInferenceTestCase;
7+
8+
final class EntityAccessControlHandlerReturnTypeExtensionTest extends TypeInferenceTestCase {
9+
10+
use AdditionalConfigFilesTrait;
11+
12+
public function dataFileAsserts(): iterable
13+
{
14+
yield from $this->gatherAssertTypes(__DIR__ . '/data/entity-access-control-handler.php');
15+
}
16+
17+
/**
18+
* @dataProvider dataFileAsserts
19+
* @param string $assertType
20+
* @param string $file
21+
* @param mixed ...$args
22+
*/
23+
public function testFileAsserts(
24+
string $assertType,
25+
string $file,
26+
...$args
27+
): void
28+
{
29+
$this->assertFileAsserts($assertType, $file, ...$args);
30+
}
31+
32+
}

tests/src/Type/data/accessible.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace PhpstanDrupalAccessible;
4+
5+
use Drupal\Core\Access\AccessResultInterface;
6+
use function PHPStan\Testing\assertType;
7+
8+
$entityTypeManager = \Drupal::entityTypeManager();
9+
$entity = $entityTypeManager->getStorage('entity_test')->create();
10+
11+
assertType('bool', $entity->access('add'));
12+
assertType('bool', $entity->access('add', null));
13+
assertType('bool', $entity->access('add', null, false));
14+
assertType(AccessResultInterface::class, $entity->access('add', null, true));
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace PhpstanDrupalEntityAccessControlHandler;
4+
5+
use Drupal\Core\Access\AccessResultInterface;
6+
use Drupal\Core\Field\FieldDefinition;
7+
use Drupal\node\Entity\Node;
8+
use function PHPStan\Testing\assertType;
9+
10+
$entityTypeManager = \Drupal::entityTypeManager();
11+
$accessControlHandler = $entityTypeManager->getAccessControlHandler('node');
12+
13+
assertType('bool', $accessControlHandler->access(Node::create(), 'view label'));
14+
assertType('bool', $accessControlHandler->access(Node::create(), 'view label', null));
15+
assertType('bool', $accessControlHandler->access(Node::create(), 'view label', null, false));
16+
assertType(AccessResultInterface::class, $accessControlHandler->access(Node::create(), 'view label', null, true));
17+
18+
assertType('bool', $accessControlHandler->createAccess());
19+
assertType('bool', $accessControlHandler->createAccess('page'));
20+
assertType('bool', $accessControlHandler->createAccess('page', null));
21+
assertType('bool', $accessControlHandler->createAccess('page', null, []));
22+
assertType('bool', $accessControlHandler->createAccess('page', null, [], false));
23+
assertType(AccessResultInterface::class, $accessControlHandler->createAccess('page', null, [], true));
24+
25+
$definition = FieldDefinition::create('string');
26+
assertType('bool', $accessControlHandler->fieldAccess('add', $definition));
27+
assertType('bool', $accessControlHandler->fieldAccess('add', $definition, null));
28+
assertType('bool', $accessControlHandler->fieldAccess('add', $definition, null, null));
29+
assertType('bool', $accessControlHandler->fieldAccess('add', $definition, null, null, false));
30+
assertType(AccessResultInterface::class, $accessControlHandler->fieldAccess('add', $definition, null, null, true));
31+

0 commit comments

Comments
 (0)