Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/Rules/Functions/FilterVarRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Php\FilterFunctionReturnTypeHelper;
use function count;

/**
* @implements Rule<Node\Expr\FuncCall>
*/
#[RegisteredRule(level: 0)]
final class FilterVarRule implements Rule
{

public function __construct(
private ReflectionProvider $reflectionProvider,
private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper,
)
{
}

public function getNodeType(): string
{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!($node->name instanceof Node\Name)) {
return [];
}

if ($this->reflectionProvider->resolveFunctionName($node->name, $scope) !== 'filter_var') {
return [];
}

$args = $node->getArgs();

if ($this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null)) {
if (count($args) < 3) {
return [];
}

$flagsType = $scope->getType($args[2]->value);

if ($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_NULL_ON_FAILURE', $flagsType)
->and($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType))
->yes()
) {
return [
RuleErrorBuilder::message('Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.')
->identifier('filterVar.nullOnFailureAndThrowOnFailure')
->build(),
];
}
}

return [];
}

}
2 changes: 1 addition & 1 deletion src/Type/Php/FilterFunctionReturnTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ private function getOptions(Type $flagsType, int $filterValue): array
/**
* @param non-empty-string $flagName
*/
private function hasFlag(string $flagName, ?Type $flagsType): TrinaryLogic
public function hasFlag(string $flagName, ?Type $flagsType): TrinaryLogic
{
$flag = $this->getConstant($flagName);
if ($flag === null) {
Expand Down
32 changes: 32 additions & 0 deletions tests/PHPStan/Rules/Functions/FilterVarRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\Php\FilterFunctionReturnTypeHelper;
use PHPUnit\Framework\Attributes\RequiresPhp;

/** @extends RuleTestCase<FilterVarRule> */
class FilterVarRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new FilterVarRule(
self::createReflectionProvider(),
self::getContainer()->getByType(FilterFunctionReturnTypeHelper::class),
);
}

#[RequiresPhp('8.5')]
public function testRule(): void
{
$this->analyse([__DIR__ . '/data/filter_var_null_and_throw.php'], [
['Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.', 5],
['Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.', 8],
['Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE.', 10],
]);
}

}
17 changes: 17 additions & 0 deletions tests/PHPStan/Rules/Functions/data/filter_var_null_and_throw.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php // lint >= 8.5

namespace FilterVarNullAndThrow;

filter_var('foo@bar.test', FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE|FILTER_NULL_ON_FAILURE);

$flag = FILTER_NULL_ON_FAILURE|FILTER_THROW_ON_FAILURE;
filter_var(100, FILTER_VALIDATE_INT, $flag);

filter_var(
'johndoe',
FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[a-z]+$/'], 'flags' => FILTER_THROW_ON_FAILURE|FILTER_NULL_ON_FAILURE]
);
filter_var('foo@bar.test', FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE);
filter_var('foo@bar.test', FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE);

Loading