diff --git a/.phpstan/ForbidEmptyRule.php b/.phpstan/ForbidEmptyRule.php new file mode 100644 index 000000000..5c89ccc80 --- /dev/null +++ b/.phpstan/ForbidEmptyRule.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\PHPStan; + +use PhpParser\Node; +use PhpParser\Node\Expr\FuncCall; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; + +/** + * PHPStan rule that forbids usage of empty() function. + * + * This rule enforces that empty() should not be used in favor of explicit checks + * like null checks, count() for arrays, or string length checks. + * + * @author Oskar Stark + * + * @implements Rule + */ +final class ForbidEmptyRule implements Rule +{ + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node instanceof FuncCall) { + return []; + } + + if (!$node->name instanceof Node\Name) { + return []; + } + + $functionName = $node->name->toString(); + + if ('empty' !== strtolower($functionName)) { + return []; + } + + // Allow empty() in ai-bundle config file where validation logic can be complex + if (str_ends_with($scope->getFile(), 'ai-bundle/config/options.php')) { + return []; + } + + return [ + RuleErrorBuilder::message( + 'Usage of empty() function is forbidden. Use explicit checks instead: null check, count() for arrays, or string length checks.' + ) + ->line($node->getLine()) + ->identifier('symfonyAi.forbidEmpty') + ->tip('Replace empty() with explicit checks like $var !== null && $var !== \'\' for strings, count($var) > 0 for arrays, etc.') + ->build(), + ]; + } +} diff --git a/.phpstan/extension.neon b/.phpstan/extension.neon index 4cfca1765..2414277a7 100644 --- a/.phpstan/extension.neon +++ b/.phpstan/extension.neon @@ -2,3 +2,4 @@ rules: - Symfony\AI\PHPStan\ForbidDeclareStrictTypesRule - Symfony\AI\PHPStan\ForbidNativeExceptionRule - Symfony\AI\PHPStan\ForbidTestCoverageAttributesRule + - Symfony\AI\PHPStan\ForbidEmptyRule diff --git a/src/agent/src/MockAgent.php b/src/agent/src/MockAgent.php index efaa54b18..195db56b2 100644 --- a/src/agent/src/MockAgent.php +++ b/src/agent/src/MockAgent.php @@ -174,7 +174,7 @@ public function getCall(int $index): array */ public function getLastCall(): array { - if (empty($this->calls)) { + if (0 === \count($this->calls)) { throw new LogicException('No calls have been made yet.'); } diff --git a/src/agent/src/StructuredOutput/AgentProcessor.php b/src/agent/src/StructuredOutput/AgentProcessor.php index d3948ba92..cea91c006 100644 --- a/src/agent/src/StructuredOutput/AgentProcessor.php +++ b/src/agent/src/StructuredOutput/AgentProcessor.php @@ -117,7 +117,7 @@ public function processOutput(Output $output): void $output->result->getMetadata()->set($originalResult->getMetadata()->all()); } - if (!empty($originalResult->getRawResult())) { + if (null !== $originalResult->getRawResult()) { $output->result->setRawResult($originalResult->getRawResult()); } } diff --git a/src/platform/src/Bridge/Anthropic/ModelClient.php b/src/platform/src/Bridge/Anthropic/ModelClient.php index 0d0ac5927..d1a2a6bea 100644 --- a/src/platform/src/Bridge/Anthropic/ModelClient.php +++ b/src/platform/src/Bridge/Anthropic/ModelClient.php @@ -47,7 +47,7 @@ public function request(Model $model, array|string $payload, array $options = [] $options['tool_choice'] = ['type' => 'auto']; } - if (isset($options['beta_features']) && \is_array($options['beta_features']) && !empty($options['beta_features'])) { + if (isset($options['beta_features']) && \is_array($options['beta_features']) && \count($options['beta_features']) > 0) { $headers['anthropic-beta'] = implode(',', $options['beta_features']); unset($options['beta_features']); }