| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 declare (strict_types=1); 4 namespace WordPress\AiClient\Files\DTO; 5 6 use WordPress\AiClient\Common\AbstractDataTransferObject; 7 use WordPress\AiClient\Common\Exception\InvalidArgumentException; 8 use WordPress\AiClient\Common\Exception\RuntimeException; 9 use WordPress\AiClient\Files\Enums\FileTypeEnum; 10 use WordPress\AiClient\Files\ValueObjects\MimeType; 11 /** 12 * Represents a file in the AI client. 13 * 14 * This DTO automatically detects whether a file is a URL, base64 data, or local file path 15 * and handles them appropriately. 16 * 17 * @since 0.1.0 18 * 19 * @phpstan-type FileArrayShape array{ 20 * fileType: string, 21 * url?: string, 22 * mimeType: string, 23 * base64Data?: string 24 * } 25 * 26 * @extends AbstractDataTransferObject<FileArrayShape> 27 */ 28 class File extends AbstractDataTransferObject 29 { 30 public const KEY_FILE_TYPE = 'fileType'; 31 public const KEY_MIME_TYPE = 'mimeType'; 32 public const KEY_URL = 'url'; 33 public const KEY_BASE64_DATA = 'base64Data'; 34 /** 35 * @var MimeType The MIME type of the file. 36 */ 37 private MimeType $mimeType; 38 /** 39 * @var FileTypeEnum The type of file storage. 40 */ 41 private FileTypeEnum $fileType; 42 /** 43 * @var string|null The URL for remote files. 44 */ 45 private ?string $url = null; 46 /** 47 * @var string|null The base64 data for inline files. 48 */ 49 private ?string $base64Data = null; 50 /** 51 * Constructor. 52 * 53 * @since 0.1.0 54 * 55 * @param string $file The file string (URL, base64 data, or local path). 56 * @param string|null $mimeType The MIME type of the file (optional). 57 * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined. 58 */ 59 public function __construct(string $file, ?string $mimeType = null) 60 { 61 // Detect and process the file type (will set MIME type if possible) 62 $this->detectAndProcessFile($file, $mimeType); 63 } 64 /** 65 * Detects the file type and processes it accordingly. 66 * 67 * @since 0.1.0 68 * 69 * @param string $file The file string to process. 70 * @param string|null $providedMimeType The explicitly provided MIME type. 71 * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined. 72 */ 73 private function detectAndProcessFile(string $file, ?string $providedMimeType): void 74 { 75 // Check if it's a URL 76 if ($this->isUrl($file)) { 77 $this->fileType = FileTypeEnum::remote(); 78 $this->url = $file; 79 $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); 80 return; 81 } 82 // Data URI pattern. 83 $dataUriPattern = '/^data:(?:([a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*' . '(?:;[a-zA-Z0-9\-]+=[a-zA-Z0-9\-]+)*)?;)?base64,([A-Za-z0-9+\/]*={0,2})$/'; 84 // Check if it's a data URI. 85 if (preg_match($dataUriPattern, $file, $matches)) { 86 $this->fileType = FileTypeEnum::inline(); 87 $this->base64Data = $matches[2]; 88 // Extract just the base64 data 89 $extractedMimeType = empty($matches[1]) ? null : $matches[1]; 90 $this->mimeType = $this->determineMimeType($providedMimeType, $extractedMimeType, null); 91 return; 92 } 93 // Check if it's a local file path (before base64 check) 94 if (file_exists($file) && is_file($file)) { 95 $this->fileType = FileTypeEnum::inline(); 96 $this->base64Data = $this->convertFileToBase64($file); 97 $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); 98 return; 99 } 100 // Check if it's plain base64 101 if (preg_match('/^[A-Za-z0-9+\/]*={0,2}$/', $file)) { 102 if ($providedMimeType === null) { 103 throw new InvalidArgumentException('MIME type is required when providing plain base64 data without data URI format.'); 104 } 105 $this->fileType = FileTypeEnum::inline(); 106 $this->base64Data = $file; 107 $this->mimeType = new MimeType($providedMimeType); 108 return; 109 } 110 throw new InvalidArgumentException('Invalid file provided. Expected URL, base64 data, or valid local file path.'); 111 } 112 /** 113 * Checks if a string is a valid URL. 114 * 115 * @since 0.1.0 116 * 117 * @param string $string The string to check. 118 * @return bool True if the string is a URL. 119 */ 120 private function isUrl(string $string): bool 121 { 122 return filter_var($string, \FILTER_VALIDATE_URL) !== \false && preg_match('/^https?:\/\//i', $string); 123 } 124 /** 125 * Converts a local file to base64. 126 * 127 * @since 0.1.0 128 * 129 * @param string $filePath The path to the local file. 130 * @return string The base64-encoded file data. 131 * @throws RuntimeException If the file cannot be read. 132 */ 133 private function convertFileToBase64(string $filePath): string 134 { 135 $fileContent = @file_get_contents($filePath); 136 if ($fileContent === \false) { 137 throw new RuntimeException(sprintf('Unable to read file: %s', $filePath)); 138 } 139 return base64_encode($fileContent); 140 } 141 /** 142 * Gets the file type. 143 * 144 * @since 0.1.0 145 * 146 * @return FileTypeEnum The file type. 147 */ 148 public function getFileType(): FileTypeEnum 149 { 150 return $this->fileType; 151 } 152 /** 153 * Checks if the file is an inline file. 154 * 155 * @since 0.1.0 156 * 157 * @return bool True if the file is inline (base64/data URI). 158 */ 159 public function isInline(): bool 160 { 161 return $this->fileType->isInline(); 162 } 163 /** 164 * Checks if the file is a remote file. 165 * 166 * @since 0.1.0 167 * 168 * @return bool True if the file is remote (URL). 169 */ 170 public function isRemote(): bool 171 { 172 return $this->fileType->isRemote(); 173 } 174 /** 175 * Gets the URL for remote files. 176 * 177 * @since 0.1.0 178 * 179 * @return string|null The URL, or null if not a remote file. 180 */ 181 public function getUrl(): ?string 182 { 183 return $this->url; 184 } 185 /** 186 * Gets the base64-encoded data for inline files. 187 * 188 * @since 0.1.0 189 * 190 * @return string|null The plain base64-encoded data (without data URI prefix), or null if not an inline file. 191 */ 192 public function getBase64Data(): ?string 193 { 194 return $this->base64Data; 195 } 196 /** 197 * Gets the data as a data URI for inline files. 198 * 199 * @since 0.1.0 200 * 201 * @return string|null The data URI in format: data:[mimeType];base64,[data], or null if not an inline file. 202 */ 203 public function getDataUri(): ?string 204 { 205 if ($this->base64Data === null) { 206 return null; 207 } 208 return sprintf('data:%s;base64,%s', $this->getMimeType(), $this->base64Data); 209 } 210 /** 211 * Gets the MIME type of the file as a string. 212 * 213 * @since 0.1.0 214 * 215 * @return string The MIME type string value. 216 */ 217 public function getMimeType(): string 218 { 219 return (string) $this->mimeType; 220 } 221 /** 222 * Gets the MIME type object. 223 * 224 * @since 0.1.0 225 * 226 * @return MimeType The MIME type object. 227 */ 228 public function getMimeTypeObject(): MimeType 229 { 230 return $this->mimeType; 231 } 232 /** 233 * Checks if the file is a video. 234 * 235 * @since 0.1.0 236 * 237 * @return bool True if the file is a video. 238 */ 239 public function isVideo(): bool 240 { 241 return $this->mimeType->isVideo(); 242 } 243 /** 244 * Checks if the file is an image. 245 * 246 * @since 0.1.0 247 * 248 * @return bool True if the file is an image. 249 */ 250 public function isImage(): bool 251 { 252 return $this->mimeType->isImage(); 253 } 254 /** 255 * Checks if the file is audio. 256 * 257 * @since 0.1.0 258 * 259 * @return bool True if the file is audio. 260 */ 261 public function isAudio(): bool 262 { 263 return $this->mimeType->isAudio(); 264 } 265 /** 266 * Checks if the file is text. 267 * 268 * @since 0.1.0 269 * 270 * @return bool True if the file is text. 271 */ 272 public function isText(): bool 273 { 274 return $this->mimeType->isText(); 275 } 276 /** 277 * Checks if the file is a document. 278 * 279 * @since 0.1.0 280 * 281 * @return bool True if the file is a document. 282 */ 283 public function isDocument(): bool 284 { 285 return $this->mimeType->isDocument(); 286 } 287 /** 288 * Checks if the file is a specific MIME type. 289 * 290 * @since 0.1.0 291 * 292 * @param string $type The mime type to check (e.g. 'image', 'text', 'video', 'audio'). 293 * 294 * @return bool True if the file is of the specified type. 295 */ 296 public function isMimeType(string $type): bool 297 { 298 return $this->mimeType->isType($type); 299 } 300 /** 301 * Determines the MIME type from various sources. 302 * 303 * @since 0.1.0 304 * 305 * @param string|null $providedMimeType The explicitly provided MIME type. 306 * @param string|null $extractedMimeType The MIME type extracted from data URI. 307 * @param string|null $pathOrUrl The file path or URL to extract extension from. 308 * @return MimeType The determined MIME type. 309 * @throws InvalidArgumentException If MIME type cannot be determined. 310 */ 311 private function determineMimeType(?string $providedMimeType, ?string $extractedMimeType, ?string $pathOrUrl): MimeType 312 { 313 // Prefer explicitly provided MIME type 314 if ($providedMimeType !== null) { 315 return new MimeType($providedMimeType); 316 } 317 // Use extracted MIME type from data URI 318 if ($extractedMimeType !== null) { 319 return new MimeType($extractedMimeType); 320 } 321 // Try to determine from file extension 322 if ($pathOrUrl !== null) { 323 $parsedUrl = parse_url($pathOrUrl); 324 $path = $parsedUrl['path'] ?? $pathOrUrl; 325 // Remove query string and fragment if present 326 $cleanPath = strtok($path, '?#'); 327 if ($cleanPath === \false) { 328 $cleanPath = $path; 329 } 330 $extension = pathinfo($cleanPath, \PATHINFO_EXTENSION); 331 if (!empty($extension)) { 332 try { 333 return MimeType::fromExtension($extension); 334 } catch (InvalidArgumentException $e) { 335 // Extension not recognized, continue to error 336 unset($e); 337 } 338 } 339 } 340 throw new InvalidArgumentException('Unable to determine MIME type. Please provide it explicitly.'); 341 } 342 /** 343 * {@inheritDoc} 344 * 345 * @since 0.1.0 346 */ 347 public static function getJsonSchema(): array 348 { 349 return ['type' => 'object', 'oneOf' => [['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::REMOTE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_URL => ['type' => 'string', 'format' => 'uri', 'description' => 'The URL to the remote file.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_URL]], ['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::INLINE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_BASE64_DATA => ['type' => 'string', 'description' => 'The base64-encoded file data.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_BASE64_DATA]]]]; 350 } 351 /** 352 * {@inheritDoc} 353 * 354 * @since 0.1.0 355 * 356 * @return FileArrayShape 357 */ 358 public function toArray(): array 359 { 360 $data = [self::KEY_FILE_TYPE => $this->fileType->value, self::KEY_MIME_TYPE => $this->getMimeType()]; 361 if ($this->url !== null) { 362 $data[self::KEY_URL] = $this->url; 363 } elseif (!$this->fileType->isRemote() && $this->base64Data !== null) { 364 $data[self::KEY_BASE64_DATA] = $this->base64Data; 365 } else { 366 throw new RuntimeException('File requires either url or base64Data. This should not be a possible condition.'); 367 } 368 return $data; 369 } 370 /** 371 * {@inheritDoc} 372 * 373 * @since 0.1.0 374 */ 375 public static function fromArray(array $array): self 376 { 377 static::validateFromArrayData($array, [self::KEY_FILE_TYPE]); 378 // Check which properties are set to determine how to construct the File 379 $mimeType = $array[self::KEY_MIME_TYPE] ?? null; 380 if (isset($array[self::KEY_URL])) { 381 return new self($array[self::KEY_URL], $mimeType); 382 } elseif (isset($array[self::KEY_BASE64_DATA])) { 383 return new self($array[self::KEY_BASE64_DATA], $mimeType); 384 } else { 385 throw new InvalidArgumentException('File requires either url or base64Data.'); 386 } 387 } 388 /** 389 * Performs a deep clone of the file. 390 * 391 * This method ensures that the MimeType value object is cloned to prevent 392 * any shared references between the original and cloned file. 393 * 394 * @since 0.4.2 395 */ 396 public function __clone() 397 { 398 $this->mimeType = clone $this->mimeType; 399 } 400 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Jun 13 09:38:55 2026 | Cross-referenced by PHPXref |