diff --git a/AGENTS.md b/AGENTS.md index 0897e0a6..7b4c7ba3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +27,44 @@ Key constraints include: * PER Coding Style (extending PSR-12). * Strict type hinting for all parameters, return values, and properties. +### Global function and constant references + +Inside a namespace, PHP resolves an unqualified function or constant name by first looking in the current namespace and only then falling back to the global namespace. That fallback is a runtime lookup on every call, and it prevents opcache from substituting the optimized handlers for common built-ins. Both `src/` and `tests/` are fully normalized to avoid it, and new code must stay that way. + +The rule is per file, based on how many times the name is referenced in that file: + +* **Referenced once:** prefix it with a leading backslash, e.g. `\gettype($value)` or `\PATHINFO_EXTENSION`. +* **Referenced two or more times:** import it at the top with `use function` or `use const`, and leave the call sites unqualified. + +```php +namespace WordPress\AiClient\Files\ValueObjects; + +use WordPress\AiClient\Common\Exception\InvalidArgumentException; + +use function sprintf; +use function strtolower; + +// ... + +if (!\is_string($other)) { // used once: leading backslash + throw new InvalidArgumentException( + sprintf('Invalid MIME type: %s', \gettype($other)) // sprintf imported, gettype used once + ); +} + +return $this->value === strtolower($other); +``` + +Notes: + +* `composer phpcs` enforces the first half of this automatically. The `SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly` rule in `phpcs.xml.dist` fails the build on any global function or constant referenced via the namespace fallback, so a bare `sprintf(...)` is a hard error. It accepts both approved forms equally, so the choice between a leading backslash and an import is a convention that reviewers need to check by eye. +* `composer phpcbf` can fix a fallback reference, but it always fixes by adding an import. For a name used only once, prefer prefixing with `\` by hand instead. +* PSR-12 treats class imports, `use function` imports, and `use const` imports as separate header blocks, each separated by a blank line and each sorted alphabetically. `composer phpcbf` fixes the spacing automatically. +* This applies to global **functions and constants** only. Classes need no equivalent treatment: PHP has no global fallback for class names, so a global class such as `Throwable` or `ReflectionClass` must already be imported or fully qualified for the code to run at all. Keep importing those with a plain `use` statement as usual. +* `true`, `false`, and `null` are language constructs, not constants, and must not be prefixed. +* Files with no namespace declaration, such as `cli.php` and `src/polyfills.php`, are already in the global namespace and need no qualification. +* Do not qualify calls to functions defined by this project or by a dependency inside a namespace; the rule covers global built-ins (including the `src/polyfills.php` shims, which are defined globally). + ## Core Principles * **Provider Agnostic:** The client is designed to work with any AI provider, avoiding vendor lock-in. @@ -79,6 +117,7 @@ For a more detailed overview, refer to the `docs/ARCHITECTURE.md` file. * **Write Tests:** All new features or bug fixes must be accompanied by corresponding unit tests. * **Use the Fluent API:** When writing examples or tests for the implementer API, prefer the fluent API for readability. * **Use `{@inheritDoc}`:** When implementing an interface method, use `{@inheritDoc}` in the PHPDoc block to avoid duplicating documentation, as specified in `CONTRIBUTING.md`. +* **Qualify Global Functions and Constants:** Within a namespace, prefix a global function or constant with `\` when it is referenced once in the file, or import it with `use function` / `use const` when it is referenced more than once. See "Global function and constant references" above. ### DON'T: @@ -101,3 +140,4 @@ All exceptions must use the project's custom exception classes rather than PHP b * **Direct HTTP Client Usage:** A common mistake is to instantiate a PSR-18 client directly in a model. This is incorrect. Instead, the model should receive an `HttpTransporter` instance and use it to send requests. * **Ignoring the Fluent API:** While the traditional API is available, the fluent API is the preferred way for implementers to use the client. Avoid writing complex, nested method calls when the fluent API provides a cleaner alternative. * **Duplicating Interface Documentation:** Manually writing PHPDoc descriptions for methods that implement an interface is a common pitfall. The `{@inheritDoc}` tag should be used instead to inherit the documentation from the interface. +* **Unqualified Global Functions:** Writing `sprintf(...)` or `is_array(...)` bare inside a namespace is easy to do by habit, but it forces a runtime namespace fallback lookup on every call. Either prefix with `\` or add a `use function` import, depending on how many times the name appears in the file. diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 2e49db44..3bc464ec 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -40,4 +40,31 @@ + + + + + + + + + + + + + + diff --git a/src/AiClient.php b/src/AiClient.php index 066bfa64..3b1d2116 100644 --- a/src/AiClient.php +++ b/src/AiClient.php @@ -19,6 +19,11 @@ use WordPress\AiClient\Results\DTO\EmbeddingResult; use WordPress\AiClient\Results\DTO\GenerativeAiResult; +use function get_class; +use function gettype; +use function is_object; +use function sprintf; + /** * Main AI Client class providing both fluent and traditional APIs for AI operations. * @@ -206,7 +211,7 @@ public static function isConfigured($availabilityOrIdOrClassName): bool } // Handle string input (provider ID or class name) via registry - if (is_string($availabilityOrIdOrClassName)) { + if (\is_string($availabilityOrIdOrClassName)) { return self::defaultRegistry()->isProviderConfigured($availabilityOrIdOrClassName); } diff --git a/src/Builders/EmbeddingBuilder.php b/src/Builders/EmbeddingBuilder.php index 2cca2a02..eab6a2b7 100644 --- a/src/Builders/EmbeddingBuilder.php +++ b/src/Builders/EmbeddingBuilder.php @@ -21,6 +21,10 @@ use WordPress\AiClient\Results\DTO\Embedding; use WordPress\AiClient\Results\DTO\EmbeddingResult; +use function count; +use function is_array; +use function sprintf; + /** * Fluent builder for generating embeddings. * @@ -71,7 +75,7 @@ public function __construct( return; } - if (is_array($input) && array_is_list($input)) { + if (is_array($input) && \array_is_list($input)) { /** @var list $input */ $this->withInput(...$input); return; @@ -256,8 +260,8 @@ private function parseInput($input): MessagePart return $this->validatePart($input); } - if (is_string($input)) { - if (trim($input) === '') { + if (\is_string($input)) { + if (\trim($input) === '') { throw new InvalidArgumentException('Cannot create an embedding input from an empty string.'); } return new MessagePart($input); diff --git a/src/Builders/MessageBuilder.php b/src/Builders/MessageBuilder.php index 6840c203..4a936dbf 100644 --- a/src/Builders/MessageBuilder.php +++ b/src/Builders/MessageBuilder.php @@ -55,7 +55,7 @@ public function __construct($input = null, ?MessageRoleEnum $role = null) // Handle different input types if ($input instanceof MessagePart) { $this->parts[] = $input; - } elseif (is_string($input)) { + } elseif (\is_string($input)) { $this->withText($input); } elseif ($input instanceof File) { $this->withFile($input); @@ -63,7 +63,7 @@ public function __construct($input = null, ?MessageRoleEnum $role = null) $this->withFunctionCall($input); } elseif ($input instanceof FunctionResponse) { $this->withFunctionResponse($input); - } elseif (is_array($input) && MessagePart::isArrayShape($input)) { + } elseif (\is_array($input) && MessagePart::isArrayShape($input)) { $this->parts[] = MessagePart::fromArray($input); } else { throw new InvalidArgumentException( @@ -141,7 +141,7 @@ public function usingModelRole(): self */ public function withText(string $text): self { - if (trim($text) === '') { + if (\trim($text) === '') { throw new InvalidArgumentException('Text content cannot be empty.'); } diff --git a/src/Builders/PromptBuilder.php b/src/Builders/PromptBuilder.php index 538392db..09b20306 100644 --- a/src/Builders/PromptBuilder.php +++ b/src/Builders/PromptBuilder.php @@ -34,6 +34,13 @@ use WordPress\AiClient\Tools\DTO\FunctionResponse; use WordPress\AiClient\Tools\DTO\WebSearch; +use function array_is_list; +use function array_merge; +use function end; +use function is_array; +use function is_string; +use function sprintf; + /** * Fluent builder for constructing AI prompts. * @@ -536,7 +543,7 @@ private function inferCapabilityFromOutputModalities(): CapabilityEnum // Multi-modal output (multiple modalities) defaults to text generation. This is temporary // as a multi-modal interface will be implemented in the future. - if (count($outputModalities) > 1) { + if (\count($outputModalities) > 1) { return CapabilityEnum::textGeneration(); } @@ -1114,7 +1121,7 @@ protected function appendPartToMessages(MessagePart $part): void if ($lastMessage instanceof Message && $lastMessage->getRole()->isUser()) { // Replace the last message with a new one containing the appended part - array_pop($this->messages); + \array_pop($this->messages); $this->messages[] = $lastMessage->withPart($part); return; } @@ -1166,7 +1173,7 @@ private function parseMessage($input, MessageRoleEnum $defaultRole): Message // Handle string input if (is_string($input)) { - if (trim($input) === '') { + if (\trim($input) === '') { throw new InvalidArgumentException('Cannot create a message from an empty string.'); } return new Message($defaultRole, [new MessagePart($input)]); @@ -1242,7 +1249,7 @@ private function validateMessages(): void ); } - $firstMessage = reset($messages); + $firstMessage = \reset($messages); if (!$firstMessage->getRole()->isUser()) { throw new InvalidArgumentException( 'The first message must be from a user role, not from ' . $firstMessage->getRole()->value diff --git a/src/Builders/Traits/ModelResolutionTrait.php b/src/Builders/Traits/ModelResolutionTrait.php index 08780903..56895935 100644 --- a/src/Builders/Traits/ModelResolutionTrait.php +++ b/src/Builders/Traits/ModelResolutionTrait.php @@ -10,6 +10,8 @@ use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; +use function array_merge; + /** * Provides shared model selection and configuration methods for builders. * diff --git a/src/Common/AbstractDataTransferObject.php b/src/Common/AbstractDataTransferObject.php index 0029ca5f..9328cc24 100644 --- a/src/Common/AbstractDataTransferObject.php +++ b/src/Common/AbstractDataTransferObject.php @@ -10,6 +10,8 @@ use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; +use function is_array; + /** * Abstract base class for all Data Value Objects in the AI Client. * @@ -46,17 +48,17 @@ protected static function validateFromArrayData(array $data, array $requiredKeys $missingKeys = []; foreach ($requiredKeys as $key) { - if (!array_key_exists($key, $data)) { + if (!\array_key_exists($key, $data)) { $missingKeys[] = $key; } } if (!empty($missingKeys)) { throw new InvalidArgumentException( - sprintf( + \sprintf( '%s::fromArray() missing required keys: %s', static::class, - implode(', ', $missingKeys) + \implode(', ', $missingKeys) ) ); } diff --git a/src/Common/AbstractEnum.php b/src/Common/AbstractEnum.php index 39d92af6..33c1f176 100644 --- a/src/Common/AbstractEnum.php +++ b/src/Common/AbstractEnum.php @@ -10,6 +10,9 @@ use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; +use function sprintf; +use function strtoupper; + /** * Abstract base class for enum-like behavior in PHP 7.4. * @@ -204,7 +207,7 @@ final public function is(self $other): bool */ final public static function getValues(): array { - return array_values(static::getConstants()); + return \array_values(static::getConstants()); } /** @@ -217,7 +220,7 @@ final public static function getValues(): array */ final public static function isValidValue(string $value): bool { - return in_array($value, self::getValues(), true); + return \in_array($value, self::getValues(), true); } /** @@ -286,7 +289,7 @@ protected static function determineClassEnumerations(string $className): array $enumConstants = []; foreach ($constants as $name => $value) { // Check if constant name follows uppercase snake_case pattern - if (!preg_match('/^[A-Z][A-Z0-9_]*$/', $name)) { + if (!\preg_match('/^[A-Z][A-Z0-9_]*$/', $name)) { throw new RuntimeException( sprintf( 'Invalid enum constant name "%s" in %s. Constants must be UPPER_SNAKE_CASE.', @@ -297,14 +300,14 @@ protected static function determineClassEnumerations(string $className): array } // Check if value is valid type - if (!is_string($value)) { + if (!\is_string($value)) { throw new RuntimeException( sprintf( 'Invalid enum value type for constant %s::%s. ' . 'Only string values are allowed, %s given.', $className, $name, - gettype($value) + \gettype($value) ) ); } @@ -328,8 +331,8 @@ protected static function determineClassEnumerations(string $className): array final public function __call(string $name, array $arguments): bool { // Handle is* methods - if (str_starts_with($name, 'is')) { - $constantName = self::camelCaseToConstant(substr($name, 2)); + if (\str_starts_with($name, 'is')) { + $constantName = self::camelCaseToConstant(\substr($name, 2)); $constants = static::getConstants(); if (isset($constants[$constantName])) { @@ -376,7 +379,7 @@ final public static function __callStatic(string $name, array $arguments): self */ private static function camelCaseToConstant(string $camelCase): string { - $snakeCase = preg_replace('/([a-z])([A-Z])/', '$1_$2', $camelCase); + $snakeCase = \preg_replace('/([a-z])([A-Z])/', '$1_$2', $camelCase); if ($snakeCase === null) { return strtoupper($camelCase); } diff --git a/src/Common/Traits/WithDataCachingTrait.php b/src/Common/Traits/WithDataCachingTrait.php index ab11ba1c..67d48013 100644 --- a/src/Common/Traits/WithDataCachingTrait.php +++ b/src/Common/Traits/WithDataCachingTrait.php @@ -63,7 +63,7 @@ protected function hasCache(string $key): bool return $cache->has($fullKey); } - return array_key_exists($fullKey, $this->localCache); + return \array_key_exists($fullKey, $this->localCache); } /** diff --git a/src/Files/DTO/File.php b/src/Files/DTO/File.php index e17b7418..e54ba915 100644 --- a/src/Files/DTO/File.php +++ b/src/Files/DTO/File.php @@ -10,6 +10,9 @@ use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\ValueObjects\MimeType; +use function preg_match; +use function sprintf; + /** * Represents a file in the AI client. * @@ -101,7 +104,7 @@ private function detectAndProcessFile(string $file, ?string $providedMimeType): } // Check if it's a local file path (before base64 check) - if (file_exists($file) && is_file($file)) { + if (\file_exists($file) && \is_file($file)) { $this->fileType = FileTypeEnum::inline(); $this->base64Data = $this->convertFileToBase64($file); $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); @@ -136,7 +139,7 @@ private function detectAndProcessFile(string $file, ?string $providedMimeType): */ private function isUrl(string $string): bool { - return filter_var($string, FILTER_VALIDATE_URL) !== false + return \filter_var($string, \FILTER_VALIDATE_URL) !== false && preg_match('/^https?:\/\//i', $string); } @@ -151,7 +154,7 @@ private function isUrl(string $string): bool */ private function convertFileToBase64(string $filePath): string { - $fileContent = @file_get_contents($filePath); + $fileContent = @\file_get_contents($filePath); if ($fileContent === false) { throw new RuntimeException( @@ -159,7 +162,7 @@ private function convertFileToBase64(string $filePath): string ); } - return base64_encode($fileContent); + return \base64_encode($fileContent); } /** @@ -364,16 +367,16 @@ private function determineMimeType( // Try to determine from file extension if ($pathOrUrl !== null) { - $parsedUrl = parse_url($pathOrUrl); + $parsedUrl = \parse_url($pathOrUrl); $path = $parsedUrl['path'] ?? $pathOrUrl; // Remove query string and fragment if present - $cleanPath = strtok($path, '?#'); + $cleanPath = \strtok($path, '?#'); if ($cleanPath === false) { $cleanPath = $path; } - $extension = pathinfo($cleanPath, PATHINFO_EXTENSION); + $extension = \pathinfo($cleanPath, \PATHINFO_EXTENSION); if (!empty($extension)) { try { return MimeType::fromExtension($extension); diff --git a/src/Files/ValueObjects/MimeType.php b/src/Files/ValueObjects/MimeType.php index ac95deed..d06de137 100644 --- a/src/Files/ValueObjects/MimeType.php +++ b/src/Files/ValueObjects/MimeType.php @@ -6,6 +6,9 @@ use WordPress\AiClient\Common\Exception\InvalidArgumentException; +use function sprintf; +use function strtolower; + /** * Value object representing a MIME type. * @@ -142,7 +145,7 @@ public function __construct(string $value) public function toExtension(): string { // Reverse lookup for the MIME type to find the extension. - $extension = array_search($this->value, self::$extensionMap, true); + $extension = \array_search($this->value, self::$extensionMap, true); if ($extension === false) { throw new InvalidArgumentException( sprintf('No known extension for MIME type: %s', $this->value) @@ -185,7 +188,7 @@ public static function fromExtension(string $extension): self public static function isValid(string $mimeType): bool { // Basic MIME type validation: type/subtype - return (bool) preg_match( + return (bool) \preg_match( '/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*$/', $mimeType ); @@ -204,7 +207,7 @@ public static function isValid(string $mimeType): bool */ public function isType(string $mimeType): bool { - return str_starts_with($this->value, strtolower($mimeType) . '/'); + return \str_starts_with($this->value, strtolower($mimeType) . '/'); } /** @@ -264,7 +267,7 @@ public function isText(): bool */ public function isDocument(): bool { - return in_array($this->value, self::$documentTypes, true); + return \in_array($this->value, self::$documentTypes, true); } /** @@ -282,12 +285,12 @@ public function equals($other): bool return $this->value === $other->value; } - if (is_string($other)) { + if (\is_string($other)) { return $this->value === strtolower($other); } throw new InvalidArgumentException( - sprintf('Invalid MIME type comparison: %s', gettype($other)) + sprintf('Invalid MIME type comparison: %s', \gettype($other)) ); } diff --git a/src/Messages/DTO/Message.php b/src/Messages/DTO/Message.php index 86ad4bc1..c8bb9214 100644 --- a/src/Messages/DTO/Message.php +++ b/src/Messages/DTO/Message.php @@ -8,6 +8,8 @@ use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; +use function array_map; + /** * Represents a message in an AI conversation. * diff --git a/src/Messages/DTO/MessagePart.php b/src/Messages/DTO/MessagePart.php index 228471be..ce4a59b0 100644 --- a/src/Messages/DTO/MessagePart.php +++ b/src/Messages/DTO/MessagePart.php @@ -97,7 +97,7 @@ public function __construct($content, ?MessagePartChannelEnum $channel = null, ? $this->channel = $channel ?? MessagePartChannelEnum::content(); $this->thoughtSignature = $thoughtSignature; - if (is_string($content)) { + if (\is_string($content)) { $this->type = MessagePartTypeEnum::text(); $this->text = $content; } elseif ($content instanceof File) { @@ -110,9 +110,9 @@ public function __construct($content, ?MessagePartChannelEnum $channel = null, ? $this->type = MessagePartTypeEnum::functionResponse(); $this->functionResponse = $content; } else { - $type = is_object($content) ? get_class($content) : gettype($content); + $type = \is_object($content) ? \get_class($content) : \gettype($content); throw new InvalidArgumentException( - sprintf( + \sprintf( 'Unsupported content type %s. Expected string, File, ' . 'FunctionCall, or FunctionResponse.', $type diff --git a/src/Messages/Util/ModalityCombinationsUtil.php b/src/Messages/Util/ModalityCombinationsUtil.php index e9ad1a1c..78b8874d 100644 --- a/src/Messages/Util/ModalityCombinationsUtil.php +++ b/src/Messages/Util/ModalityCombinationsUtil.php @@ -33,7 +33,7 @@ class ModalityCombinationsUtil public static function buildCombinations(array $required, array $optional): array { $combinations = []; - $count = count($optional); + $count = \count($optional); $subsetCount = 1 << $count; // 2^count. for ($i = 0; $i < $subsetCount; $i++) { diff --git a/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php b/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php index 52c0cd5f..5db56c1f 100644 --- a/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php +++ b/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php @@ -47,7 +47,7 @@ abstract class AbstractApiBasedModelMetadataDirectory implements final public function listModelMetadata(): array { $modelsMetadata = $this->getModelMetadataMap(); - return array_values($modelsMetadata); + return \array_values($modelsMetadata); } /** @@ -71,7 +71,7 @@ final public function getModelMetadata(string $modelId): ModelMetadata $modelsMetadata = $this->getModelMetadataMap(); if (!isset($modelsMetadata[$modelId])) { throw new InvalidArgumentException( - sprintf('No model with ID %s was found in the provider', $modelId) + \sprintf('No model with ID %s was found in the provider', $modelId) ); } return $modelsMetadata[$modelId]; @@ -111,7 +111,7 @@ protected function getCachedKeys(): array */ protected function getBaseCacheKey(): string { - return 'ai_client_' . AiClient::VERSION . '_' . md5(static::class); + return 'ai_client_' . AiClient::VERSION . '_' . \md5(static::class); } /** diff --git a/src/Providers/ApiBasedImplementation/AbstractApiProvider.php b/src/Providers/ApiBasedImplementation/AbstractApiProvider.php index 3b5d3658..08705982 100644 --- a/src/Providers/ApiBasedImplementation/AbstractApiProvider.php +++ b/src/Providers/ApiBasedImplementation/AbstractApiProvider.php @@ -48,6 +48,6 @@ public static function url(string $path = ''): string return static::baseUrl(); } - return static::baseUrl() . '/' . ltrim($path, '/'); + return static::baseUrl() . '/' . \ltrim($path, '/'); } } diff --git a/src/Providers/DTO/ProviderMetadata.php b/src/Providers/DTO/ProviderMetadata.php index a19e6d56..815c0827 100644 --- a/src/Providers/DTO/ProviderMetadata.php +++ b/src/Providers/DTO/ProviderMetadata.php @@ -101,9 +101,9 @@ public function __construct( ?string $description = null, ?string $logoPath = null ) { - if (!preg_match('/^[a-z0-9\-_]+$/', $id)) { + if (!\preg_match('/^[a-z0-9\-_]+$/', $id)) { throw new InvalidArgumentException( - sprintf( + \sprintf( // phpcs:ignore Generic.Files.LineLength.TooLong 'Invalid provider ID "%s". Only lowercase alphanumeric characters, hyphens, and underscores are allowed.', $id @@ -239,7 +239,7 @@ public static function getJsonSchema(): array ], self::KEY_AUTHENTICATION_METHOD => [ 'type' => ['string', 'null'], - 'enum' => array_merge(RequestAuthenticationMethod::getValues(), [null]), + 'enum' => \array_merge(RequestAuthenticationMethod::getValues(), [null]), 'description' => 'The authentication method.', ], self::KEY_LOGO_PATH => [ diff --git a/src/Providers/DTO/ProviderModelsMetadata.php b/src/Providers/DTO/ProviderModelsMetadata.php index 9e3b0f9c..70222cf0 100644 --- a/src/Providers/DTO/ProviderModelsMetadata.php +++ b/src/Providers/DTO/ProviderModelsMetadata.php @@ -8,6 +8,8 @@ use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; +use function array_map; + /** * Represents metadata about a provider and its available models. * @@ -53,7 +55,7 @@ class ProviderModelsMetadata extends AbstractDataTransferObject */ public function __construct(ProviderMetadata $provider, array $models) { - if (!array_is_list($models)) { + if (!\array_is_list($models)) { throw new InvalidArgumentException('Models must be a list array.'); } diff --git a/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php b/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php index f14b36ee..cbd5991e 100644 --- a/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php +++ b/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php @@ -30,7 +30,7 @@ abstract class AbstractClientDiscoveryStrategy implements DiscoveryStrategy */ public static function init(): void { - if (!class_exists('\Http\Discovery\Psr18ClientDiscovery')) { + if (!\class_exists('\Http\Discovery\Psr18ClientDiscovery')) { return; } @@ -67,7 +67,7 @@ public static function getCandidates($type) 'Psr\Http\Message\UriFactoryInterface', ]; - if (in_array($type, $psr17Factories, true)) { + if (\in_array($type, $psr17Factories, true)) { return [ [ 'class' => Psr17Factory::class, diff --git a/src/Providers/Http/Collections/HeadersCollection.php b/src/Providers/Http/Collections/HeadersCollection.php index 9a211bbb..29096095 100644 --- a/src/Providers/Http/Collections/HeadersCollection.php +++ b/src/Providers/Http/Collections/HeadersCollection.php @@ -4,6 +4,8 @@ namespace WordPress\AiClient\Providers\Http\Collections; +use function strtolower; + /** * Simple collection for managing HTTP headers with case-insensitive access. * @@ -80,7 +82,7 @@ public function getAll(): array public function getAsString(string $name): ?string { $values = $this->get($name); - return $values !== null ? implode(', ', $values) : null; + return $values !== null ? \implode(', ', $values) : null; } /** @@ -107,11 +109,11 @@ public function has(string $name): bool */ private function set(string $name, $value): void { - if (is_array($value)) { - $normalizedValues = array_values($value); + if (\is_array($value)) { + $normalizedValues = \array_values($value); } else { // Split comma-separated string into array - $normalizedValues = array_map('trim', explode(',', $value)); + $normalizedValues = \array_map('trim', \explode(',', $value)); } $lowerName = strtolower($name); diff --git a/src/Providers/Http/DTO/Request.php b/src/Providers/Http/DTO/Request.php index 4dafd3de..7afa8fe6 100644 --- a/src/Providers/Http/DTO/Request.php +++ b/src/Providers/Http/DTO/Request.php @@ -11,6 +11,10 @@ use WordPress\AiClient\Providers\Http\Collections\HeadersCollection; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; +use function http_build_query; +use function is_array; +use function is_string; + /** * Represents an HTTP request. * @@ -153,7 +157,7 @@ public function getUri(): string { // If GET request with data, append as query parameters if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) { - $separator = str_contains($this->uri, '?') ? '&' : '?'; + $separator = \str_contains($this->uri, '?') ? '&' : '?'; return $this->uri . $separator . http_build_query($this->data); } @@ -242,8 +246,8 @@ public function getBody(): ?string $contentType = $this->getContentType(); // JSON encoding - if ($contentType !== null && stripos($contentType, 'application/json') !== false) { - return json_encode($this->data, JSON_THROW_ON_ERROR); + if ($contentType !== null && \stripos($contentType, 'application/json') !== false) { + return \json_encode($this->data, \JSON_THROW_ON_ERROR); } // Default to URL encoding for forms diff --git a/src/Providers/Http/DTO/RequestOptions.php b/src/Providers/Http/DTO/RequestOptions.php index 9b101bef..b66332a4 100644 --- a/src/Providers/Http/DTO/RequestOptions.php +++ b/src/Providers/Http/DTO/RequestOptions.php @@ -247,7 +247,7 @@ private function validateTimeout(?float $value, string $fieldName): void { if ($value !== null && $value < 0) { throw new InvalidArgumentException( - sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName) + \sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName) ); } } diff --git a/src/Providers/Http/DTO/Response.php b/src/Providers/Http/DTO/Response.php index 623ab770..9def154f 100644 --- a/src/Providers/Http/DTO/Response.php +++ b/src/Providers/Http/DTO/Response.php @@ -185,14 +185,14 @@ public function getData(): ?array return null; } - $data = json_decode($this->body, true); + $data = \json_decode($this->body, true); - if (json_last_error() !== JSON_ERROR_NONE) { + if (\json_last_error() !== \JSON_ERROR_NONE) { return null; } /** @var array|null $data */ - return is_array($data) ? $data : null; + return \is_array($data) ? $data : null; } /** diff --git a/src/Providers/Http/Enums/HttpMethodEnum.php b/src/Providers/Http/Enums/HttpMethodEnum.php index 8ecad16a..f08a781a 100644 --- a/src/Providers/Http/Enums/HttpMethodEnum.php +++ b/src/Providers/Http/Enums/HttpMethodEnum.php @@ -6,6 +6,8 @@ use WordPress\AiClient\Common\AbstractEnum; +use function in_array; + /** * Represents HTTP request methods. * diff --git a/src/Providers/Http/Exception/ClientException.php b/src/Providers/Http/Exception/ClientException.php index 0429c8f5..90964beb 100644 --- a/src/Providers/Http/Exception/ClientException.php +++ b/src/Providers/Http/Exception/ClientException.php @@ -9,6 +9,8 @@ use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor; +use function sprintf; + /** * Exception thrown for 4xx HTTP client errors. * diff --git a/src/Providers/Http/Exception/NetworkException.php b/src/Providers/Http/Exception/NetworkException.php index ecfa3d49..f8dcb689 100644 --- a/src/Providers/Http/Exception/NetworkException.php +++ b/src/Providers/Http/Exception/NetworkException.php @@ -57,7 +57,7 @@ public function getRequest(): Request public static function fromPsr18NetworkException(RequestInterface $psrRequest, \Throwable $networkException): self { $request = Request::fromPsrRequest($psrRequest); - $message = sprintf( + $message = \sprintf( 'Network error occurred while sending request to %s: %s', $request->getUri(), $networkException->getMessage() diff --git a/src/Providers/Http/Exception/RedirectException.php b/src/Providers/Http/Exception/RedirectException.php index 57da329d..80697821 100644 --- a/src/Providers/Http/Exception/RedirectException.php +++ b/src/Providers/Http/Exception/RedirectException.php @@ -7,6 +7,8 @@ use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Response; +use function sprintf; + /** * Exception thrown for 3xx HTTP redirect responses. * diff --git a/src/Providers/Http/Exception/ResponseException.php b/src/Providers/Http/Exception/ResponseException.php index 01e2bd70..b6724c27 100644 --- a/src/Providers/Http/Exception/ResponseException.php +++ b/src/Providers/Http/Exception/ResponseException.php @@ -6,6 +6,8 @@ use WordPress\AiClient\Common\Exception\RuntimeException; +use function sprintf; + /** * Exception class for HTTP response errors. * diff --git a/src/Providers/Http/Exception/ServerException.php b/src/Providers/Http/Exception/ServerException.php index bc4b63de..5fa0e26c 100644 --- a/src/Providers/Http/Exception/ServerException.php +++ b/src/Providers/Http/Exception/ServerException.php @@ -8,6 +8,8 @@ use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor; +use function sprintf; + /** * Exception thrown for 5xx HTTP server errors. * diff --git a/src/Providers/Http/HttpTransporter.php b/src/Providers/Http/HttpTransporter.php index 00917cec..c1b992c1 100644 --- a/src/Providers/Http/HttpTransporter.php +++ b/src/Providers/Http/HttpTransporter.php @@ -91,7 +91,7 @@ public function send(Request $request, ?RequestOptions $options = null): Respons } catch (\Psr\Http\Client\ClientExceptionInterface $e) { // Handle other PSR-18 client exceptions that are not network-related throw new RuntimeException( - sprintf( + \sprintf( 'HTTP client error occurred while sending request to %s: %s', $request->getUri(), $e->getMessage() @@ -173,7 +173,7 @@ private function isGuzzleClient(ClientInterface $client): bool { $reflection = new \ReflectionObject($client); - if (!is_callable([$client, 'send'])) { + if (!\is_callable([$client, 'send'])) { return false; } @@ -189,7 +189,7 @@ private function isGuzzleClient(ClientInterface $client): bool $parameters = $method->getParameters(); - if (count($parameters) < 2) { + if (\count($parameters) < 2) { return false; } @@ -198,7 +198,7 @@ private function isGuzzleClient(ClientInterface $client): bool return false; } - if (!is_a($firstParameter->getName(), RequestInterface::class, true)) { + if (!\is_a($firstParameter->getName(), RequestInterface::class, true)) { return false; } diff --git a/src/Providers/Http/Util/ErrorMessageExtractor.php b/src/Providers/Http/Util/ErrorMessageExtractor.php index c31cd5b7..18a9de44 100644 --- a/src/Providers/Http/Util/ErrorMessageExtractor.php +++ b/src/Providers/Http/Util/ErrorMessageExtractor.php @@ -4,6 +4,9 @@ namespace WordPress\AiClient\Providers\Http\Util; +use function is_array; +use function is_string; + /** * Utility for extracting error messages from API response data. * diff --git a/src/Providers/Http/Util/ResponseUtil.php b/src/Providers/Http/Util/ResponseUtil.php index 18177bed..0c28dbd8 100644 --- a/src/Providers/Http/Util/ResponseUtil.php +++ b/src/Providers/Http/Util/ResponseUtil.php @@ -58,7 +58,7 @@ public static function throwIfNotSuccessful(Response $response): void } throw new \RuntimeException( - sprintf('Response returned invalid status code: %s', $response->getStatusCode()) + \sprintf('Response returned invalid status code: %s', $response->getStatusCode()) ); } } diff --git a/src/Providers/ModelResolver.php b/src/Providers/ModelResolver.php index c8aeefe5..36388cdd 100644 --- a/src/Providers/ModelResolver.php +++ b/src/Providers/ModelResolver.php @@ -12,6 +12,10 @@ use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; +use function is_string; +use function reset; +use function sprintf; + /** * Resolves the concrete AI model to use based on selection preferences. * @@ -124,9 +128,9 @@ public function setModelPreferences(...$preferredModels): void $preferenceKeys = []; foreach ($preferredModels as $preferredModel) { - if (is_array($preferredModel)) { + if (\is_array($preferredModel)) { // [model identifier, provider ID] tuple - if (!array_is_list($preferredModel) || count($preferredModel) !== 2) { + if (!\array_is_list($preferredModel) || \count($preferredModel) !== 2) { throw new InvalidArgumentException( 'Model preference tuple must contain model identifier and provider ID.' ); @@ -277,14 +281,14 @@ public function resolve( // Check if any preferred models match the candidates, in priority order. if (!empty($this->modelPreferenceKeys)) { // Find preferences that match available candidates, preserving preference order. - $matchingPreferences = array_intersect_key( - array_flip($this->modelPreferenceKeys), + $matchingPreferences = \array_intersect_key( + \array_flip($this->modelPreferenceKeys), $candidateMap ); if (!empty($matchingPreferences)) { // Get the first matching preference key - $firstMatchKey = key($matchingPreferences); + $firstMatchKey = \key($matchingPreferences); [$providerId, $modelId] = $candidateMap[$firstMatchKey]; $model = $this->registry->getProviderModel($providerId, $modelId, $modelConfig); @@ -428,7 +432,7 @@ private function normalizePreferenceIdentifier( throw new InvalidArgumentException($emptyMessage); } - $trimmed = trim($value); + $trimmed = \trim($value); if ($trimmed === '') { throw new InvalidArgumentException($emptyMessage); } diff --git a/src/Providers/Models/DTO/ModelConfig.php b/src/Providers/Models/DTO/ModelConfig.php index fad995d0..411f1399 100644 --- a/src/Providers/Models/DTO/ModelConfig.php +++ b/src/Providers/Models/DTO/ModelConfig.php @@ -12,6 +12,9 @@ use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\WebSearch; +use function array_is_list; +use function array_map; + /** * Represents configuration for an AI model. * @@ -698,7 +701,7 @@ public function getOutputMediaOrientation(): ?MediaOrientationEnum */ public function setOutputMediaAspectRatio(string $outputMediaAspectRatio): void { - if (!preg_match('/^\d+:\d+$/', $outputMediaAspectRatio)) { + if (!\preg_match('/^\d+:\d+$/', $outputMediaAspectRatio)) { throw new InvalidArgumentException( 'Output media aspect ratio must be in the format "width:height" (e.g. 3:2, 16:9).' ); @@ -736,7 +739,7 @@ protected function validateMediaOrientationAspectRatioCompatibility( MediaOrientationEnum $orientation, string $aspectRatio ): void { - $aspectRatioParts = explode(':', $aspectRatio); + $aspectRatioParts = \explode(':', $aspectRatio); if ($orientation->isSquare() && $aspectRatioParts[0] !== $aspectRatioParts[1]) { throw new InvalidArgumentException( 'The aspect ratio "' . $aspectRatio . '" is not compatible with the square orientation.' diff --git a/src/Providers/Models/DTO/ModelMetadata.php b/src/Providers/Models/DTO/ModelMetadata.php index 529f090f..d84a1ac5 100644 --- a/src/Providers/Models/DTO/ModelMetadata.php +++ b/src/Providers/Models/DTO/ModelMetadata.php @@ -8,6 +8,9 @@ use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; +use function array_is_list; +use function array_map; + /** * Represents metadata about an AI model. * diff --git a/src/Providers/Models/DTO/ModelRequirements.php b/src/Providers/Models/DTO/ModelRequirements.php index 492e5fd8..5d0095ae 100644 --- a/src/Providers/Models/DTO/ModelRequirements.php +++ b/src/Providers/Models/DTO/ModelRequirements.php @@ -12,6 +12,13 @@ use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; +use function array_is_list; +use function array_map; +use function array_unique; +use function array_values; + +use const SORT_REGULAR; + /** * Represents requirements that implementing code has for AI model selection. * @@ -155,7 +162,7 @@ public static function fromPromptData(CapabilityEnum $capability, array $message $inputModalities = []; // Check if we have chat history (multiple messages) - if (count($messages) > 1) { + if (\count($messages) > 1) { $capabilities[] = CapabilityEnum::chatHistory(); } diff --git a/src/Providers/Models/DTO/SupportedOption.php b/src/Providers/Models/DTO/SupportedOption.php index 3a0e25d6..cca301e9 100644 --- a/src/Providers/Models/DTO/SupportedOption.php +++ b/src/Providers/Models/DTO/SupportedOption.php @@ -9,6 +9,8 @@ use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; +use function is_array; + /** * Represents a supported configuration option for an AI model. * @@ -51,7 +53,7 @@ class SupportedOption extends AbstractDataTransferObject */ public function __construct(OptionEnum $name, ?array $supportedValues = null) { - if ($supportedValues !== null && !array_is_list($supportedValues)) { + if ($supportedValues !== null && !\array_is_list($supportedValues)) { throw new InvalidArgumentException('Supported values must be a list array.'); } @@ -144,8 +146,8 @@ private static function normalizeValue($value) */ private static function normalizeArrayForComparison(array $items): array { - $normalized = array_map([self::class, 'normalizeValue'], $items); - sort($normalized); + $normalized = \array_map([self::class, 'normalizeValue'], $items); + \sort($normalized); return $normalized; } diff --git a/src/Providers/Models/Enums/OptionEnum.php b/src/Providers/Models/Enums/OptionEnum.php index dde22ae2..c73a9891 100644 --- a/src/Providers/Models/Enums/OptionEnum.php +++ b/src/Providers/Models/Enums/OptionEnum.php @@ -99,13 +99,13 @@ protected static function determineClassEnumerations(string $className): array // Add ModelConfig constants that start with KEY_ foreach ($modelConfigConstants as $constantName => $constantValue) { - if (str_starts_with($constantName, 'KEY_')) { + if (\str_starts_with($constantName, 'KEY_')) { // Remove KEY_ prefix to get the enum constant name - $enumConstantName = substr($constantName, 4); + $enumConstantName = \substr($constantName, 4); // The value is the snake_case version stored in ModelConfig // ModelConfig already stores these as snake_case strings - if (is_string($constantValue)) { + if (\is_string($constantValue)) { $constants[$enumConstantName] = $constantValue; } } diff --git a/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php b/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php index 482e9909..84be27ed 100644 --- a/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php +++ b/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php @@ -23,6 +23,9 @@ use WordPress\AiClient\Results\DTO\TokenUsage; use WordPress\AiClient\Results\Enums\FinishReasonEnum; +use function is_array; +use function is_string; + /** * Base class for an image generation model for providers that implement OpenAI's API format. * @@ -125,7 +128,7 @@ protected function prepareGenerateImageParams(array $prompt): array $outputMimeType = $config->getOutputMimeType(); if ($outputMimeType !== null) { - $params['output_format'] = preg_replace('/^image\//', '', $outputMimeType); + $params['output_format'] = \preg_replace('/^image\//', '', $outputMimeType); } $outputMediaOrientation = $config->getOutputMediaOrientation(); @@ -142,7 +145,7 @@ protected function prepareGenerateImageParams(array $prompt): array foreach ($customOptions as $key => $value) { if (isset($params[$key])) { throw new InvalidArgumentException( - sprintf( + \sprintf( 'The custom option "%s" conflicts with an existing parameter.', $key ) @@ -166,7 +169,7 @@ protected function prepareGenerateImageParams(array $prompt): array */ protected function preparePromptParam(array $messages): string { - if (count($messages) !== 1) { + if (\count($messages) !== 1) { throw new InvalidArgumentException( 'The API requires a single user message as prompt.' ); @@ -304,7 +307,7 @@ protected function parseResponseToGenerativeAiResult( $candidates = []; foreach ($responseData['data'] as $index => $choiceData) { - if (!is_array($choiceData) || array_is_list($choiceData)) { + if (!is_array($choiceData) || \array_is_list($choiceData)) { throw ResponseException::fromInvalidData( $this->providerMetadata()->getName(), "data[{$index}]", diff --git a/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php b/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php index e0e2e71b..7669a292 100644 --- a/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php +++ b/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php @@ -25,6 +25,16 @@ use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; +use function array_filter; +use function array_is_list; +use function array_map; +use function array_values; +use function count; +use function is_array; +use function is_string; +use function json_encode; +use function sprintf; + /** * Base class for a text generation model for providers that implement OpenAI's API format. * @@ -247,7 +257,7 @@ function (Message $message): array { ); if ($systemInstruction) { - array_unshift( + \array_unshift( $messagesParam, [ /* @@ -766,7 +776,7 @@ protected function parseResponseChoiceMessageToolCallPart(array $toolCallData): } $functionArguments = is_string($toolCallData['function']['arguments']) - ? json_decode($toolCallData['function']['arguments'], true) + ? \json_decode($toolCallData['function']['arguments'], true) : $toolCallData['function']['arguments']; $functionCall = new FunctionCall( diff --git a/src/Providers/ProviderRegistry.php b/src/Providers/ProviderRegistry.php index d5aa6e47..72273f41 100644 --- a/src/Providers/ProviderRegistry.php +++ b/src/Providers/ProviderRegistry.php @@ -22,6 +22,14 @@ use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; +use function get_class; +use function is_array; +use function is_subclass_of; +use function preg_replace; +use function sprintf; +use function str_replace; +use function strtoupper; + /** * Registry for managing AI providers and their models. * @@ -63,7 +71,7 @@ class ProviderRegistry implements WithHttpTransporterInterface */ public function registerProvider(string $className): void { - if (!class_exists($className)) { + if (!\class_exists($className)) { throw new InvalidArgumentException( sprintf('Provider class does not exist: %s', $className) ); @@ -136,7 +144,7 @@ public function registerProvider(string $className): void */ public function getRegisteredProviderIds(): array { - return array_keys($this->registeredIdsToClassNames); + return \array_keys($this->registeredIdsToClassNames); } /** @@ -540,13 +548,13 @@ private function createDefaultProviderRequestAuthentication( $envVarName = $this->getEnvVarName($providerId, $property); // Try to get the value from environment variable or constant. - $envValue = getenv($envVarName); + $envValue = \getenv($envVarName); if ($envValue === false) { - if (!defined($envVarName)) { + if (!\defined($envVarName)) { continue; // Skip if neither environment variable nor constant is defined. } - $envValue = constant($envVarName); - if (!is_scalar($envValue)) { + $envValue = \constant($envVarName); + if (!\is_scalar($envValue)) { continue; } } @@ -554,7 +562,7 @@ private function createDefaultProviderRequestAuthentication( if (isset($details['type'])) { switch ($details['type']) { case 'boolean': - $authenticationData[$property] = filter_var($envValue, FILTER_VALIDATE_BOOLEAN); + $authenticationData[$property] = \filter_var($envValue, \FILTER_VALIDATE_BOOLEAN); break; case 'number': $authenticationData[$property] = (int) $envValue; @@ -573,7 +581,7 @@ private function createDefaultProviderRequestAuthentication( if (isset($authenticationSchema['required']) && is_array($authenticationSchema['required'])) { /** @var list $requiredProperties */ $requiredProperties = $authenticationSchema['required']; - if (array_diff_key(array_flip($requiredProperties), $authenticationData)) { + if (\array_diff_key(\array_flip($requiredProperties), $authenticationData)) { return null; } } diff --git a/src/Results/DTO/Embedding.php b/src/Results/DTO/Embedding.php index 076109c6..d2125192 100644 --- a/src/Results/DTO/Embedding.php +++ b/src/Results/DTO/Embedding.php @@ -11,6 +11,8 @@ use Traversable; use WordPress\AiClient\Common\Exception\InvalidArgumentException; +use function array_is_list; + /** * Represents a single generated embedding vector. * @@ -54,7 +56,7 @@ public function __construct(array $values, int $dimensions) throw new InvalidArgumentException('Embedding values must be integers or floats.'); } - if (count($values) !== $dimensions) { + if (\count($values) !== $dimensions) { throw new InvalidArgumentException('Embedding vector length must match dimensions.'); } @@ -74,12 +76,12 @@ public function __construct(array $values, int $dimensions) */ private static function isEmbeddingList($values): bool { - if (!is_array($values) || !array_is_list($values)) { + if (!\is_array($values) || !array_is_list($values)) { return false; } foreach ($values as $value) { - if (!is_int($value) && !is_float($value)) { + if (!\is_int($value) && !\is_float($value)) { return false; } } diff --git a/src/Results/DTO/EmbeddingResult.php b/src/Results/DTO/EmbeddingResult.php index d5ebbed1..c0777644 100644 --- a/src/Results/DTO/EmbeddingResult.php +++ b/src/Results/DTO/EmbeddingResult.php @@ -270,7 +270,7 @@ public function toArray(): array { $data = [ self::KEY_ID => $this->id, - self::KEY_EMBEDDINGS => array_map( + self::KEY_EMBEDDINGS => \array_map( static fn (Embedding $embedding): array => $embedding->toArray(), $this->embeddings ), diff --git a/src/Results/DTO/GenerativeAiResult.php b/src/Results/DTO/GenerativeAiResult.php index eae903de..cf848cd6 100644 --- a/src/Results/DTO/GenerativeAiResult.php +++ b/src/Results/DTO/GenerativeAiResult.php @@ -13,6 +13,11 @@ use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\Contracts\ResultInterface; +use function array_filter; +use function array_map; +use function array_values; +use function sprintf; + /** * Represents the result of a generative AI operation. * @@ -183,7 +188,7 @@ public function getAdditionalData(): array */ public function getCandidateCount(): int { - return count($this->candidates); + return \count($this->candidates); } /** diff --git a/tests/integration/Anthropic/FunctionCallingIntegrationTest.php b/tests/integration/Anthropic/FunctionCallingIntegrationTest.php index a4f66096..adfb0643 100644 --- a/tests/integration/Anthropic/FunctionCallingIntegrationTest.php +++ b/tests/integration/Anthropic/FunctionCallingIntegrationTest.php @@ -14,6 +14,8 @@ use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\FunctionResponse; +use function stripos; + /** * Integration tests for Anthropic function calling. * diff --git a/tests/integration/Google/FunctionCallingIntegrationTest.php b/tests/integration/Google/FunctionCallingIntegrationTest.php index 9670b5b7..5489f220 100644 --- a/tests/integration/Google/FunctionCallingIntegrationTest.php +++ b/tests/integration/Google/FunctionCallingIntegrationTest.php @@ -14,6 +14,8 @@ use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\FunctionResponse; +use function stripos; + /** * Integration tests for Google function calling. * diff --git a/tests/integration/OpenAi/EmbeddingGenerationIntegrationTest.php b/tests/integration/OpenAi/EmbeddingGenerationIntegrationTest.php index 2970238b..80aa2925 100644 --- a/tests/integration/OpenAi/EmbeddingGenerationIntegrationTest.php +++ b/tests/integration/OpenAi/EmbeddingGenerationIntegrationTest.php @@ -10,6 +10,8 @@ use WordPress\AiClient\Results\DTO\EmbeddingResult; use WordPress\AiClient\Tests\integration\traits\IntegrationTestTrait; +use function count; + /** * Integration tests for OpenAI embedding generation. * diff --git a/tests/integration/OpenAi/FunctionCallingIntegrationTest.php b/tests/integration/OpenAi/FunctionCallingIntegrationTest.php index 88229e15..4c2f1716 100644 --- a/tests/integration/OpenAi/FunctionCallingIntegrationTest.php +++ b/tests/integration/OpenAi/FunctionCallingIntegrationTest.php @@ -14,6 +14,8 @@ use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\FunctionResponse; +use function stripos; + /** * Integration tests for OpenAI function calling. * diff --git a/tests/integration/traits/IntegrationTestTrait.php b/tests/integration/traits/IntegrationTestTrait.php index 11d122a8..7357b1ac 100644 --- a/tests/integration/traits/IntegrationTestTrait.php +++ b/tests/integration/traits/IntegrationTestTrait.php @@ -20,7 +20,7 @@ trait IntegrationTestTrait protected function requireApiKey(string $envVar): void { // Check both $_ENV (populated by symfony/dotenv) and getenv() (shell environment) - $value = $_ENV[$envVar] ?? getenv($envVar); + $value = $_ENV[$envVar] ?? \getenv($envVar); if ($value === false || $value === '' || $value === null) { $this->markTestSkipped("Skipping: {$envVar} environment variable is not set."); } diff --git a/tests/mocks/MockCache.php b/tests/mocks/MockCache.php index d6429de4..54a5b651 100644 --- a/tests/mocks/MockCache.php +++ b/tests/mocks/MockCache.php @@ -132,7 +132,7 @@ public function deleteMultiple($keys): bool public function has($key): bool { $this->operations[] = ['operation' => 'has', 'key' => $key]; - return array_key_exists($key, $this->cache); + return \array_key_exists($key, $this->cache); } /** @@ -153,7 +153,7 @@ public function getOperations(): array */ public function getOperationsOfType(string $operation): array { - return array_values(array_filter( + return \array_values(\array_filter( $this->operations, static function (array $op) use ($operation): bool { return $op['operation'] === $operation; diff --git a/tests/mocks/MockEventDispatcher.php b/tests/mocks/MockEventDispatcher.php index f7e5507c..c4822642 100644 --- a/tests/mocks/MockEventDispatcher.php +++ b/tests/mocks/MockEventDispatcher.php @@ -34,7 +34,7 @@ public function dispatch(object $event): object { $this->dispatchedEvents[] = $event; - $eventClass = get_class($event); + $eventClass = \get_class($event); if (isset($this->listeners[$eventClass])) { foreach ($this->listeners[$eventClass] as $listener) { $listener($event); @@ -78,7 +78,7 @@ public function getDispatchedEvents(): array */ public function getDispatchedEventsOfType(string $eventClass): array { - return array_values(array_filter( + return \array_values(\array_filter( $this->dispatchedEvents, static function (object $event) use ($eventClass): bool { return $event instanceof $eventClass; diff --git a/tests/mocks/MockModelMetadataDirectory.php b/tests/mocks/MockModelMetadataDirectory.php index 9ade638d..af6162da 100644 --- a/tests/mocks/MockModelMetadataDirectory.php +++ b/tests/mocks/MockModelMetadataDirectory.php @@ -43,7 +43,7 @@ public function __construct(array $models = []) */ public function listModelMetadata(): array { - return array_values($this->models); + return \array_values($this->models); } /** @@ -61,7 +61,7 @@ public function getModelMetadata(string $modelId): ModelMetadata { if (!isset($this->models[$modelId])) { throw new InvalidArgumentException( - sprintf('Model not found: %s', $modelId) + \sprintf('Model not found: %s', $modelId) ); } diff --git a/tests/traits/ArrayTransformationTestTrait.php b/tests/traits/ArrayTransformationTestTrait.php index 159b573f..16a11525 100644 --- a/tests/traits/ArrayTransformationTestTrait.php +++ b/tests/traits/ArrayTransformationTestTrait.php @@ -51,7 +51,7 @@ protected function assertToArrayReturnsArray($object): array protected function assertArrayRoundTrip($original, callable $assertCallback): void { $array = $original->toArray(); - $className = get_class($original); + $className = \get_class($original); $restored = $className::fromArray($array); $this->assertInstanceOf($className, $restored, 'fromArray() should return instance of ' . $className); diff --git a/tests/traits/EnumTestTrait.php b/tests/traits/EnumTestTrait.php index e4bb34c7..536df88f 100644 --- a/tests/traits/EnumTestTrait.php +++ b/tests/traits/EnumTestTrait.php @@ -7,6 +7,8 @@ use BadMethodCallException; use WordPress\AiClient\Common\AbstractEnum; +use function reset; + /** * Trait for testing enum classes. */ @@ -37,7 +39,7 @@ public function testEnumHasExpectedValues(): void $actualValues = $enumClass::getValues(); // Since getValues() now returns just the values, we need to extract values from expected - $expectedValuesList = array_values($expectedValues); + $expectedValuesList = \array_values($expectedValues); $this->assertEquals($expectedValuesList, $actualValues); } @@ -52,7 +54,7 @@ public function testEnumCasesReturnCorrectInstances(): void $cases = $enumClass::cases(); - $this->assertCount(count($expectedValues), $cases); + $this->assertCount(\count($expectedValues), $cases); foreach ($cases as $case) { $this->assertInstanceOf($enumClass, $case); diff --git a/tests/traits/MockModelCreationTrait.php b/tests/traits/MockModelCreationTrait.php index 17119f81..9a26db78 100644 --- a/tests/traits/MockModelCreationTrait.php +++ b/tests/traits/MockModelCreationTrait.php @@ -103,7 +103,7 @@ protected function createTestEmbeddingResult(?array $embeddings = null): Embeddi return new EmbeddingResult( 'test-embedding-result-id', $embeddings, - count($embeddings[0]), + \count($embeddings[0]), new TokenUsage(10, 0, 10), $providerMetadata, $modelMetadata diff --git a/tests/unit/AiClientTest.php b/tests/unit/AiClientTest.php index 8eed1337..f9139938 100644 --- a/tests/unit/AiClientTest.php +++ b/tests/unit/AiClientTest.php @@ -208,7 +208,7 @@ public function testGenerateEmbeddingsReturnsBatchVectors(): void $embeddings = AiClient::generateEmbeddings(['First prompt', 'Second prompt'], $mockModel, $registry); - $this->assertSame($expectedEmbeddings, array_map( + $this->assertSame($expectedEmbeddings, \array_map( static fn ($embedding): array => $embedding->getValues(), $embeddings )); diff --git a/tests/unit/Builders/EmbeddingBuilderTest.php b/tests/unit/Builders/EmbeddingBuilderTest.php index bb40f658..3adeff1a 100644 --- a/tests/unit/Builders/EmbeddingBuilderTest.php +++ b/tests/unit/Builders/EmbeddingBuilderTest.php @@ -126,7 +126,7 @@ public function testGenerateEmbeddingsReturnsBatchVectors(): void $builder = new EmbeddingBuilder($this->registry, ['First input', 'Second input']); $builder->usingModel($model); - $this->assertSame($embeddings, array_map( + $this->assertSame($embeddings, \array_map( static fn ($embedding): array => $embedding->getValues(), $builder->generateEmbeddings() )); diff --git a/tests/unit/Common/AbstractDataTransferObjectTest.php b/tests/unit/Common/AbstractDataTransferObjectTest.php index fecb9387..8fe06861 100644 --- a/tests/unit/Common/AbstractDataTransferObjectTest.php +++ b/tests/unit/Common/AbstractDataTransferObjectTest.php @@ -11,6 +11,8 @@ use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface; use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface; +use function json_encode; + /** * Tests for the AbstractDataTransferObject class. * @@ -97,7 +99,7 @@ public static function getJsonSchema(): array // Verify JSON encoding produces correct output $json = json_encode($result); $this->assertIsString($json); - $decoded = json_decode($json, true); + $decoded = \json_decode($json, true); // In JSON, empty object should be {} not [] $this->assertStringContainsString('"emptyObject":{}', $json); diff --git a/tests/unit/Common/AbstractEnumTest.php b/tests/unit/Common/AbstractEnumTest.php index ed9944f4..628a4d41 100644 --- a/tests/unit/Common/AbstractEnumTest.php +++ b/tests/unit/Common/AbstractEnumTest.php @@ -12,6 +12,8 @@ use WordPress\AiClient\Tests\mocks\Enums\InvalidTypeTestEnum; use WordPress\AiClient\Tests\mocks\Enums\ValidTestEnum; +use function array_map; + /** * @covers \WordPress\AiClient\Common\AbstractEnum */ diff --git a/tests/unit/Exceptions/ExceptionsTest.php b/tests/unit/Exceptions/ExceptionsTest.php index 8dd5315c..d915d7e7 100644 --- a/tests/unit/Exceptions/ExceptionsTest.php +++ b/tests/unit/Exceptions/ExceptionsTest.php @@ -14,6 +14,8 @@ use WordPress\AiClient\Providers\Http\Exception\NetworkException; use WordPress\AiClient\Providers\Http\Exception\ServerException; +use function json_encode; + /** * Tests for AI Client exceptions. * diff --git a/tests/unit/Files/DTO/FileTest.php b/tests/unit/Files/DTO/FileTest.php index 9ac76659..e064fc52 100644 --- a/tests/unit/Files/DTO/FileTest.php +++ b/tests/unit/Files/DTO/FileTest.php @@ -11,6 +11,11 @@ use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\ValueObjects\MimeType; +use function file_put_contents; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; + /** * @covers \WordPress\AiClient\Files\DTO\File */ @@ -142,7 +147,7 @@ public function testCreateFromLocalFile(): void $this->assertEquals(FileTypeEnum::inline(), $file->getFileType()); $this->assertNull($file->getUrl()); - $this->assertEquals(base64_encode('Hello World'), $file->getBase64Data()); + $this->assertEquals(\base64_encode('Hello World'), $file->getBase64Data()); $this->assertEquals('text/plain', $file->getMimeType()); } finally { unlink($tempFile); @@ -183,8 +188,8 @@ public function testNonExistentLocalFileThrowsException(): void public function testDirectoryThrowsException(): void { // Create a directory instead of a file - $tempDir = sys_get_temp_dir() . '/test_dir_' . uniqid(); - mkdir($tempDir); + $tempDir = sys_get_temp_dir() . '/test_dir_' . \uniqid(); + \mkdir($tempDir); try { $this->expectException(InvalidArgumentException::class); @@ -194,7 +199,7 @@ public function testDirectoryThrowsException(): void new File($tempDir, 'text/plain'); } finally { - rmdir($tempDir); + \rmdir($tempDir); } } diff --git a/tests/unit/Messages/DTO/MessagePartTest.php b/tests/unit/Messages/DTO/MessagePartTest.php index de7a4ae0..d1c82789 100644 --- a/tests/unit/Messages/DTO/MessagePartTest.php +++ b/tests/unit/Messages/DTO/MessagePartTest.php @@ -118,7 +118,7 @@ public function testCreateWithEmptyString(): void public function testUnsupportedContentThrowsException($content, string $expectedType): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf( + $this->expectExceptionMessage(\sprintf( 'Unsupported content type %s. Expected string, File, FunctionCall, or FunctionResponse.', $expectedType )); diff --git a/tests/unit/Messages/DTO/MessageTest.php b/tests/unit/Messages/DTO/MessageTest.php index 3635f83b..a722bb85 100644 --- a/tests/unit/Messages/DTO/MessageTest.php +++ b/tests/unit/Messages/DTO/MessageTest.php @@ -351,7 +351,7 @@ public function testArrayRoundTrip(): void $restored = Message::fromArray($json); $this->assertEquals($original->getRole()->value, $restored->getRole()->value); - $this->assertCount(count($original->getParts()), $restored->getParts()); + $this->assertCount(\count($original->getParts()), $restored->getParts()); $this->assertEquals($original->getParts()[0]->getText(), $restored->getParts()[0]->getText()); $this->assertEquals( $original->getParts()[1]->getFile()->getUrl(), diff --git a/tests/unit/Messages/DTO/ModelMessageTest.php b/tests/unit/Messages/DTO/ModelMessageTest.php index 44345816..b4f2a097 100644 --- a/tests/unit/Messages/DTO/ModelMessageTest.php +++ b/tests/unit/Messages/DTO/ModelMessageTest.php @@ -180,7 +180,7 @@ public function testArrayRoundTripWithFunctionCall(): void ]), function ($original, $restored) { $this->assertEquals($original->getRole()->value, $restored->getRole()->value); - $this->assertCount(count($original->getParts()), $restored->getParts()); + $this->assertCount(\count($original->getParts()), $restored->getParts()); $this->assertEquals( $original->getParts()[0]->getText(), $restored->getParts()[0]->getText() diff --git a/tests/unit/Messages/DTO/UserMessageTest.php b/tests/unit/Messages/DTO/UserMessageTest.php index c2de91aa..e3fdf2c4 100644 --- a/tests/unit/Messages/DTO/UserMessageTest.php +++ b/tests/unit/Messages/DTO/UserMessageTest.php @@ -288,7 +288,7 @@ public function testArrayRoundTrip(): void ]), function ($original, $restored) { $this->assertEquals($original->getRole()->value, $restored->getRole()->value); - $this->assertCount(count($original->getParts()), $restored->getParts()); + $this->assertCount(\count($original->getParts()), $restored->getParts()); $this->assertEquals( $original->getParts()[0]->getText(), $restored->getParts()[0]->getText() diff --git a/tests/unit/Messages/Util/ModalityCombinationsUtilTest.php b/tests/unit/Messages/Util/ModalityCombinationsUtilTest.php index 0db9bb1b..2fffffca 100644 --- a/tests/unit/Messages/Util/ModalityCombinationsUtilTest.php +++ b/tests/unit/Messages/Util/ModalityCombinationsUtilTest.php @@ -8,6 +8,9 @@ use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Messages\Util\ModalityCombinationsUtil; +use function array_map; +use function count; + /** * @covers \WordPress\AiClient\Messages\Util\ModalityCombinationsUtil */ @@ -96,13 +99,13 @@ static function (ModalityEnum $m): string { }, $combo ); - sort($values); - return implode(',', $values); + \sort($values); + return \implode(',', $values); }, $combinations ); - $this->assertCount(count($normalised), array_unique($normalised)); + $this->assertCount(count($normalised), \array_unique($normalised)); } /** @@ -153,10 +156,10 @@ public function testBuildCombinationsEachOptionalAppearsInExactlyHalfTheCombinat foreach ($optional as $modality) { $appearances = count( - array_filter( + \array_filter( $combinations, static function (array $combo) use ($modality): bool { - return in_array($modality, $combo, true); + return \in_array($modality, $combo, true); } ) ); @@ -164,7 +167,7 @@ static function (array $combo) use ($modality): bool { $this->assertSame( (int) $expectedAppearances, $appearances, - sprintf('Modality "%s" should appear in exactly half the combinations.', $modality->value) + \sprintf('Modality "%s" should appear in exactly half the combinations.', $modality->value) ); } } diff --git a/tests/unit/Providers/DTO/ProviderMetadataTest.php b/tests/unit/Providers/DTO/ProviderMetadataTest.php index 53df16af..b6f9ae0d 100644 --- a/tests/unit/Providers/DTO/ProviderMetadataTest.php +++ b/tests/unit/Providers/DTO/ProviderMetadataTest.php @@ -234,8 +234,8 @@ public function testArrayRoundTrip(): void public function testJsonSerialize(): void { $metadata = new ProviderMetadata('json-provider', 'JSON Provider', ProviderTypeEnum::cloud()); - $json = json_encode($metadata); - $decoded = json_decode($json, true); + $json = \json_encode($metadata); + $decoded = \json_decode($json, true); $this->assertIsString($json); $this->assertIsArray($decoded); diff --git a/tests/unit/Providers/DTO/ProviderModelsMetadataTest.php b/tests/unit/Providers/DTO/ProviderModelsMetadataTest.php index e95196fd..80c5a3d6 100644 --- a/tests/unit/Providers/DTO/ProviderModelsMetadataTest.php +++ b/tests/unit/Providers/DTO/ProviderModelsMetadataTest.php @@ -16,6 +16,8 @@ use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; +use function count; + /** * @covers \WordPress\AiClient\Providers\DTO\ProviderModelsMetadata */ @@ -263,8 +265,8 @@ public function testJsonSerialize(): void [$this->createModelMetadata('json-model', 'JSON Model')] ); - $json = json_encode($metadata); - $decoded = json_decode($json, true); + $json = \json_encode($metadata); + $decoded = \json_decode($json, true); $this->assertIsString($json); $this->assertIsArray($decoded); @@ -346,7 +348,10 @@ public function testNumericArrayKeysPreserved(): void // Ensure models array has numeric keys starting from 0 $this->assertArrayHasKey(0, $array[ProviderModelsMetadata::KEY_MODELS]); $this->assertArrayHasKey(1, $array[ProviderModelsMetadata::KEY_MODELS]); - $this->assertEquals(['models' => array_keys($array[ProviderModelsMetadata::KEY_MODELS])], ['models' => [0, 1]]); + $this->assertEquals( + ['models' => \array_keys($array[ProviderModelsMetadata::KEY_MODELS])], + ['models' => [0, 1]] + ); } /** diff --git a/tests/unit/Providers/Models/DTO/ModelConfigTest.php b/tests/unit/Providers/Models/DTO/ModelConfigTest.php index b9819479..de031115 100644 --- a/tests/unit/Providers/Models/DTO/ModelConfigTest.php +++ b/tests/unit/Providers/Models/DTO/ModelConfigTest.php @@ -16,6 +16,8 @@ use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\WebSearch; +use function array_keys; + /** * @covers \WordPress\AiClient\Providers\Models\DTO\ModelConfig */ @@ -521,8 +523,8 @@ public function testJsonSerialize(): void $config->setMaxTokens(1000); $config->setSystemInstruction('JSON test'); - $json = json_encode($config); - $decoded = json_decode($json, true); + $json = \json_encode($config); + $decoded = \json_decode($json, true); $this->assertIsString($json); $this->assertIsArray($decoded); diff --git a/tests/unit/Providers/Models/DTO/ModelMetadataTest.php b/tests/unit/Providers/Models/DTO/ModelMetadataTest.php index 13192572..c66875f8 100644 --- a/tests/unit/Providers/Models/DTO/ModelMetadataTest.php +++ b/tests/unit/Providers/Models/DTO/ModelMetadataTest.php @@ -13,6 +13,9 @@ use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; +use function array_keys; +use function count; + /** * @covers \WordPress\AiClient\Providers\Models\DTO\ModelMetadata */ @@ -270,8 +273,8 @@ public function testJsonSerialize(): void [new SupportedOption(OptionEnum::outputSchema(), [256, 512, 1024])] ); - $json = json_encode($metadata); - $decoded = json_decode($json, true); + $json = \json_encode($metadata); + $decoded = \json_decode($json, true); $this->assertIsString($json); $this->assertIsArray($decoded); @@ -320,7 +323,7 @@ public function testWithAllCapabilities(): void $this->assertCount(count($allCapabilities), $array[ModelMetadata::KEY_SUPPORTED_CAPABILITIES]); // Verify all capabilities are preserved - $expectedValues = array_map(function ($cap) { + $expectedValues = \array_map(function ($cap) { return $cap->value; }, $allCapabilities); $this->assertEquals($expectedValues, $array[ModelMetadata::KEY_SUPPORTED_CAPABILITIES]); diff --git a/tests/unit/Providers/Models/DTO/ModelRequirementsTest.php b/tests/unit/Providers/Models/DTO/ModelRequirementsTest.php index ddc8f157..3836aad0 100644 --- a/tests/unit/Providers/Models/DTO/ModelRequirementsTest.php +++ b/tests/unit/Providers/Models/DTO/ModelRequirementsTest.php @@ -20,6 +20,11 @@ use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; +use function array_filter; +use function array_keys; +use function array_values; +use function count; + /** * @covers \WordPress\AiClient\Providers\Models\DTO\ModelRequirements */ @@ -234,8 +239,8 @@ public function testJsonSerialize(): void [new RequiredOption(OptionEnum::outputSchema(), 1536)] ); - $json = json_encode($requirements); - $decoded = json_decode($json, true); + $json = \json_encode($requirements); + $decoded = \json_decode($json, true); $this->assertIsString($json); $this->assertIsArray($decoded); @@ -273,7 +278,7 @@ public function testWithAllCapabilityTypes(): void $this->assertCount(count($allCapabilities), $array[ModelRequirements::KEY_REQUIRED_CAPABILITIES]); // Verify all capabilities are preserved with correct values - $expectedValues = array_map(function ($cap) { + $expectedValues = \array_map(function ($cap) { return $cap->value; }, $allCapabilities); $this->assertEquals($expectedValues, $array[ModelRequirements::KEY_REQUIRED_CAPABILITIES]); diff --git a/tests/unit/Providers/Models/DTO/RequiredOptionTest.php b/tests/unit/Providers/Models/DTO/RequiredOptionTest.php index b10187af..385784b2 100644 --- a/tests/unit/Providers/Models/DTO/RequiredOptionTest.php +++ b/tests/unit/Providers/Models/DTO/RequiredOptionTest.php @@ -160,7 +160,7 @@ public function testGetJsonSchema(): void $this->assertCount(6, $schema['properties'][RequiredOption::KEY_VALUE]['oneOf']); // Verify all allowed types - $types = array_map(function ($item) { + $types = \array_map(function ($item) { return $item['type']; }, $schema['properties'][RequiredOption::KEY_VALUE]['oneOf']); $this->assertContains('string', $types); @@ -326,8 +326,8 @@ public function testJsonSerialize(): void { $option = new RequiredOption(OptionEnum::outputSchema(), ['enabled' => true, 'count' => 5]); - $json = json_encode($option); - $decoded = json_decode($json, true); + $json = \json_encode($option); + $decoded = \json_decode($json, true); $this->assertIsString($json); $this->assertIsArray($decoded); diff --git a/tests/unit/Providers/Models/DTO/SupportedOptionTest.php b/tests/unit/Providers/Models/DTO/SupportedOptionTest.php index 552c4b7b..fcebc02b 100644 --- a/tests/unit/Providers/Models/DTO/SupportedOptionTest.php +++ b/tests/unit/Providers/Models/DTO/SupportedOptionTest.php @@ -12,6 +12,11 @@ use WordPress\AiClient\Providers\Models\DTO\SupportedOption; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; +use function json_decode; +use function json_encode; +use function serialize; +use function unserialize; + /** * @covers \WordPress\AiClient\Providers\Models\DTO\SupportedOption */ @@ -191,7 +196,7 @@ public function testGetJsonSchema(): void $this->assertArrayHasKey('oneOf', $schema['properties'][SupportedOption::KEY_SUPPORTED_VALUES]['items']); // Verify all allowed types in items - $types = array_map(function ($item) { + $types = \array_map(function ($item) { return $item['type']; }, $schema['properties'][SupportedOption::KEY_SUPPORTED_VALUES]['items']['oneOf']); $this->assertContains('string', $types); @@ -378,7 +383,7 @@ public function testArrayValuesProperlyIndexed(): void $array = $option->toArray(); // Ensure supportedValues array has numeric keys starting from 0 - $this->assertEquals([0, 1, 2], array_keys($array[SupportedOption::KEY_SUPPORTED_VALUES])); + $this->assertEquals([0, 1, 2], \array_keys($array[SupportedOption::KEY_SUPPORTED_VALUES])); } /** diff --git a/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModelTest.php b/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModelTest.php index b16b68f9..da75df38 100644 --- a/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModelTest.php +++ b/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModelTest.php @@ -25,6 +25,8 @@ use WordPress\AiClient\Results\Enums\FinishReasonEnum; use WordPress\AiClient\Tests\mocks\MockOpenAiCompatibleImageGenerationModel; +use function json_encode; + /** * @covers \WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleImageGenerationModel */ diff --git a/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectoryTest.php b/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectoryTest.php index f8e79541..23b4d046 100644 --- a/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectoryTest.php +++ b/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectoryTest.php @@ -13,6 +13,8 @@ use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Tests\mocks\MockCache; +use function md5; + /** * @covers \WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleModelMetadataDirectory */ diff --git a/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModelTest.php b/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModelTest.php index 6c99c75b..bed5cb42 100644 --- a/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModelTest.php +++ b/tests/unit/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModelTest.php @@ -27,6 +27,8 @@ use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\FunctionResponse; +use function json_encode; + /** * @covers \WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleTextGenerationModel */ @@ -1156,7 +1158,7 @@ public function testParseResponseChoiceToCandidateInvalidFinishReason(): void $this->expectException(ResponseException::class); $this->expectExceptionMessage( - sprintf( + \sprintf( 'Unexpected TestProvider API response: Invalid "%s" key: Invalid finish reason "unknown".', 'choices[0].finish_reason' ) diff --git a/tests/unit/Providers/OpenAiCompatibleImplementation/MockOpenAiCompatibleModelMetadataDirectory.php b/tests/unit/Providers/OpenAiCompatibleImplementation/MockOpenAiCompatibleModelMetadataDirectory.php index 0b647b61..fa499643 100644 --- a/tests/unit/Providers/OpenAiCompatibleImplementation/MockOpenAiCompatibleModelMetadataDirectory.php +++ b/tests/unit/Providers/OpenAiCompatibleImplementation/MockOpenAiCompatibleModelMetadataDirectory.php @@ -101,9 +101,9 @@ protected function parseResponseToModelMetadataList(Response $response): array { $data = $response->getData(); $modelsMetadata = []; - if (isset($data['data']) && is_array($data['data'])) { + if (isset($data['data']) && \is_array($data['data'])) { foreach ($data['data'] as $modelData) { - if (isset($modelData['id']) && is_string($modelData['id'])) { + if (isset($modelData['id']) && \is_string($modelData['id'])) { if ($this->useRealModelMetadata) { $modelsMetadata[] = $this->createRealModelMetadata($modelData['id']); } elseif ($this->modelMetadataStubFactory !== null) { @@ -126,7 +126,7 @@ private function createRealModelMetadata(string $modelId): ModelMetadata { return new ModelMetadata( $modelId, - ucfirst($modelId), + \ucfirst($modelId), [CapabilityEnum::textGeneration()], [] ); diff --git a/tests/unit/Providers/ProviderRegistryTest.php b/tests/unit/Providers/ProviderRegistryTest.php index ff8197e6..10fbf956 100644 --- a/tests/unit/Providers/ProviderRegistryTest.php +++ b/tests/unit/Providers/ProviderRegistryTest.php @@ -21,6 +21,8 @@ use WordPress\AiClient\Tests\mocks\MockProvider; use WordPress\AiClient\Tests\mocks\MockProviderAvailability; +use function putenv; + /** * @covers \WordPress\AiClient\Providers\ProviderRegistry */ diff --git a/tests/unit/Results/DTO/CandidateTest.php b/tests/unit/Results/DTO/CandidateTest.php index 385347ae..91888ff4 100644 --- a/tests/unit/Results/DTO/CandidateTest.php +++ b/tests/unit/Results/DTO/CandidateTest.php @@ -360,7 +360,7 @@ public function testArrayRoundTrip(): void function ($original, $restored) { $this->assertEquals($original->getFinishReason()->value, $restored->getFinishReason()->value); $this->assertCount( - count($original->getMessage()->getParts()), + \count($original->getMessage()->getParts()), $restored->getMessage()->getParts() ); $this->assertEquals( diff --git a/tests/unit/Results/DTO/EmbeddingTest.php b/tests/unit/Results/DTO/EmbeddingTest.php index 00d17aa5..2341d68b 100644 --- a/tests/unit/Results/DTO/EmbeddingTest.php +++ b/tests/unit/Results/DTO/EmbeddingTest.php @@ -19,7 +19,7 @@ public function testGettersAndArrayConversion(): void $this->assertSame([0.1, 1, 0.3], $embedding->getValues()); $this->assertSame(3, $embedding->getDimensions()); - $this->assertSame(3, count($embedding)); + $this->assertSame(3, \count($embedding)); $this->assertSame([0.1, 1, 0.3], $embedding->toArray()); $this->assertSame([0.1, 1, 0.3], $embedding->jsonSerialize()); } diff --git a/tests/unit/Results/DTO/GenerativeAiResultTest.php b/tests/unit/Results/DTO/GenerativeAiResultTest.php index ae1fc7bc..b7f46b20 100644 --- a/tests/unit/Results/DTO/GenerativeAiResultTest.php +++ b/tests/unit/Results/DTO/GenerativeAiResultTest.php @@ -882,7 +882,7 @@ public function testArrayRoundTripWithMultipleCandidates(): void ), function ($original, $restored) { $this->assertEquals($original->getId(), $restored->getId()); - $this->assertCount(count($original->getCandidates()), $restored->getCandidates()); + $this->assertCount(\count($original->getCandidates()), $restored->getCandidates()); $this->assertEquals( $original->getTokenUsage()->getTotalTokens(), $restored->getTokenUsage()->getTotalTokens()