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()
Comment on lines +54 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you should also report maybe if reportMaybe is enabled.

But I'm unsure how good those filterFunctionReturnTypeHelper::hasFlag works so far

) {
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