Skip to content

Commit 2c670a2

Browse files
committed
add new rule for filter_var
1 parent 80f4734 commit 2c670a2

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\FuncCall;
7+
use PhpParser\Node\Name;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\DependencyInjection\RegisteredRule;
10+
use PHPStan\Reflection\ReflectionProvider;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use PHPStan\Type\Php\FilterFunctionReturnTypeHelper;
14+
use function count;
15+
16+
/**
17+
* @implements Rule<Node\Expr\FuncCall>
18+
*/
19+
#[RegisteredRule(level: 0)]
20+
final class FilterVarRule implements Rule
21+
{
22+
23+
public function __construct(
24+
private ReflectionProvider $reflectionProvider,
25+
private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper,
26+
)
27+
{
28+
}
29+
30+
public function getNodeType(): string
31+
{
32+
return FuncCall::class;
33+
}
34+
35+
public function processNode(Node $node, Scope $scope): array
36+
{
37+
if (!($node->name instanceof Node\Name)) {
38+
return [];
39+
}
40+
41+
if ($this->reflectionProvider->resolveFunctionName($node->name, $scope) !== 'filter_var') {
42+
return [];
43+
}
44+
45+
$args = $node->getArgs();
46+
47+
if ($this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null)) {
48+
if (count($args) < 3) {
49+
return [];
50+
}
51+
52+
$flagsType = $scope->getType($args[2]->value);
53+
54+
if ($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_NULL_ON_FAILURE', $flagsType)
55+
->and($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType))
56+
->yes()
57+
) {
58+
return [
59+
RuleErrorBuilder::message('Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.')
60+
->identifier('filterVar.nullOnFailureAndThrowOnFailure')
61+
->build(),
62+
];
63+
}
64+
}
65+
66+
return [];
67+
}
68+
69+
}

src/Type/Php/FilterFunctionReturnTypeHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ private function getOptions(Type $flagsType, int $filterValue): array
483483
/**
484484
* @param non-empty-string $flagName
485485
*/
486-
private function hasFlag(string $flagName, ?Type $flagsType): TrinaryLogic
486+
public function hasFlag(string $flagName, ?Type $flagsType): TrinaryLogic
487487
{
488488
$flag = $this->getConstant($flagName);
489489
if ($flag === null) {
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 PHPStan\Rules\Functions;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
use PHPStan\Type\Php\FilterFunctionReturnTypeHelper;
8+
use PHPUnit\Framework\Attributes\RequiresPhp;
9+
10+
/** @extends RuleTestCase<FilterVarRule> */
11+
class FilterVarRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new FilterVarRule(
17+
self::createReflectionProvider(),
18+
self::getContainer()->getByType(FilterFunctionReturnTypeHelper::class),
19+
);
20+
}
21+
22+
#[RequiresPhp('8.5')]
23+
public function testRule(): void
24+
{
25+
$this->analyse([__DIR__ . '/data/filter_var_null_and_throw.php'], [
26+
['Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.', 5],
27+
['Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.', 8],
28+
['Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.', 10],
29+
]);
30+
}
31+
32+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php // lint >= 8.5
2+
3+
namespace FilterVarNullAndThrow;
4+
5+
filter_var('[email protected]', FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE|FILTER_NULL_ON_FAILURE);
6+
7+
$flag = FILTER_NULL_ON_FAILURE|FILTER_THROW_ON_FAILURE;
8+
filter_var(100, FILTER_VALIDATE_INT, $flag);
9+
10+
filter_var(
11+
'johndoe',
12+
FILTER_VALIDATE_REGEXP,
13+
['options' => ['regexp' => '/^[a-z]+$/'], 'flags' => FILTER_THROW_ON_FAILURE|FILTER_NULL_ON_FAILURE]
14+
);
15+
filter_var('[email protected]', FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE);
16+
filter_var('[email protected]', FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE);
17+

0 commit comments

Comments
 (0)