Created
March 8, 2022 12:40
-
-
Save matthiasnoback/2ae71a53cce0cf18878687df65e518e6 to your computer and use it in GitHub Desktop.
PHPStan rule and test combined
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace Utils\PHPStan; | |
use Generator; | |
use Illuminate\Database\Eloquent\Model; | |
use PhpParser\Node; | |
use PhpParser\Node\Expr\PropertyFetch; | |
use PHPStan\Analyser\Scope; | |
use PHPStan\Rules\Rule; | |
use PHPStan\Rules\RuleErrorBuilder; | |
use PHPStan\Testing\RuleTestCase; | |
use PHPStan\Type\ObjectType; | |
final class EloquentModelAttributesRule extends RuleTestCase implements Rule | |
{ | |
public function getNodeType(): string | |
{ | |
return PropertyFetch::class; | |
} | |
/** | |
* @param PropertyFetch $node | |
*/ | |
public function processNode(Node $node, Scope $scope): array | |
{ | |
$isMethodCallOnModel = (new ObjectType(Model::class)) | |
->isSuperTypeOf($scope->getType($node->var)); | |
if ($isMethodCallOnModel->no()) { | |
return []; | |
} | |
// The object on which the property is fetched is a Model | |
if ($scope->isInClass() | |
&& $scope->getClassReflection() !== null | |
&& $scope->getClassReflection()->isSubclassOf( | |
Model::class | |
)) { | |
// The property fetch takes place inside a Model class | |
return []; | |
} | |
return [ | |
RuleErrorBuilder::message( | |
'Accessing attributes is not allowed outside a Model' | |
)->build(), | |
]; | |
} | |
/** | |
* @dataProvider provideSnippetsAndExpectedErrors | |
*/ | |
public function testRule(string $code, array $expectedErrors): void | |
{ | |
$file = sys_get_temp_dir() . '/' . md5($code) . '.php'; | |
file_put_contents($file, $code); | |
try { | |
$this->analyse([$file], $expectedErrors); | |
} finally { | |
unlink($file); | |
} | |
} | |
/** | |
* @return Generator<string, {string, array<{string, int}>}> | |
*/ | |
public function provideSnippetsAndExpectedErrors(): Generator | |
{ | |
yield 'ERROR: property assignment is done on a Model from the outside' => [ | |
<<<'PHP' | |
<?php | |
$model = new class extends \Illuminate\Database\Eloquent\Model {}; | |
$model->foo = 'bar'; | |
PHP | |
, | |
[ | |
[ | |
'Accessing attributes is not allowed outside a Model', | |
3, | |
], | |
], | |
]; | |
yield 'ERROR: property fetch is done on a Model from the outside' => [ | |
<<<'PHP' | |
<?php | |
$model = new class extends \Illuminate\Database\Eloquent\Model {}; | |
$foo = $model->foo; | |
PHP | |
, | |
[ | |
[ | |
'Accessing attributes is not allowed outside a Model', | |
3, | |
], | |
], | |
]; | |
yield 'OK: The object is not a Model' => [ | |
<<<'PHP' | |
<?php | |
$notAModel = new class() {}; | |
$foo = $notAModel->foo; | |
PHP | |
, | |
[], | |
]; | |
yield 'OK: The object is a Model' => [ | |
<<<'PHP' | |
<?php | |
$model = new class {}; | |
$model->foo = 'foo'; | |
PHP | |
, | |
[], | |
]; | |
yield 'OK: attribute is fetched inside a Model' => [ | |
<<<'PHP' | |
<?php | |
class Foo extends \Illuminate\Database\Eloquent\Model { | |
public function foo(): void { | |
$foo = $this->foo; | |
} | |
}; | |
PHP | |
, | |
[], | |
]; | |
yield 'OK: attribute is assigned inside a Model' => [ | |
<<<'PHP' | |
<?php | |
class Foo extends \Illuminate\Database\Eloquent\Model { | |
public function foo(): void { | |
$this->foo = 'foo'; | |
} | |
}; | |
PHP | |
, | |
[], | |
]; | |
} | |
protected function getRule(): Rule | |
{ | |
return new self(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<phpunit bootstrap="vendor/autoload.php"> | |
<testsuites> | |
<testsuite name="Tests"> | |
<directory>test</directory> | |
</testsuite> | |
<testsuite name="PHPStan rules"> | |
<directory suffix="Rule.php">utils/PHPStan</directory> | |
</testsuite> | |
</testsuites> | |
</phpunit> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment