diff --git a/.fern/metadata.json b/.fern/metadata.json index 1d23241..1d7116a 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -11,10 +11,10 @@ "homepage": "https://www.cloudpdf.com" } }, - "originGitCommit": "81e6a2097d2997ef713a3013668e5eae0e748575", + "originGitCommit": "32635f40577b214f6a6b3189a2271425c3a574fd", "originGitCommitIsDirty": false, "invokedBy": "ci", - "requestedVersion": "3.0.0-alpha.5", + "requestedVersion": "3.0.0-alpha.6", "ciProvider": "github", - "sdkVersion": "3.0.0-alpha.5" + "sdkVersion": "3.0.0-alpha.6" } \ No newline at end of file diff --git a/cloudpdf-generation.json b/cloudpdf-generation.json index cfc99ca..3d2276a 100644 --- a/cloudpdf-generation.json +++ b/cloudpdf-generation.json @@ -1,12 +1,12 @@ { "language": "php", - "canonicalVersion": "3.0.0-next.5", - "sdkVersion": "3.0.0-alpha.5", + "canonicalVersion": "3.0.0-next.6", + "sdkVersion": "3.0.0-alpha.6", "source": { "repository": "embedpdf/embed-pdf-viewer", "openapi": "cloudpdf/contract/openapi.json", - "openapiSha256": "e86b3f1766a77c28cb782a6513b5a86998668cd4e688e5b5ac27342e58637cd7", - "gitCommit": "81e6a2097d2997ef713a3013668e5eae0e748575", + "openapiSha256": "b3e236a932673e27e05825352e1bc3675369d7505040bf7d7760e542aae8d38d", + "gitCommit": "32635f40577b214f6a6b3189a2271425c3a574fd", "gitCommitIsDirty": false }, "fern": { diff --git a/reference.md b/reference.md index 527f175..7895f05 100644 --- a/reference.md +++ b/reference.md @@ -1574,6 +1574,123 @@ $client->documents->uploadProxy( + + + + +
$client->documents->importFrom($tenantId, $request) -> ?DocumentsImportFrom200Response +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Default mode is synchronous and bounded: the response returns only after the transfer verified and committed (or failed). mode=async (connection sources only) answers 202 immediately and an in-process worker performs the transfer with leased, fenced retries; poll the document until ready/failed. The deployment import policy gates scheme, network range, and size; sources must declare a length. CloudPDF copies and owns the bytes — the source is never referenced in place. A 502 marks a retryable upstream failure: retry with the same idempotencyKey to resume the same document. URL sources are capabilities and never echoed back. Connection sources name operator-registered storage (bucket/prefix scope, allowed credential classes, and tenant bindings are deployment configuration); `revision` is provider-interpreted (S3 VersionId, GCS generation, Azure version id). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->documents->importFrom( + 'tenantId', + new DocumentsImportFromRequest([ + 'source' => DocumentsImportFromRequestSource::url(new DocumentsImportFromRequestSourceUrl([ + 'url' => 'url', + ])), + ]), +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**$tenantId:** `string` + +
+
+ +
+
+ +**$source:** `DocumentsImportFromRequestSource` + +
+
+ +
+
+ +**$expected:** `?DocumentsImportFromRequestExpected` + +
+
+ +
+
+ +**$metadata:** `?array` + +
+
+ +
+
+ +**$idempotencyKey:** `?string` + +
+
+ +
+
+ +**$dedupMode:** `?string` + +
+
+ +
+
+ +**$docId:** `?string` + +
+
+ +
+
+ +**$mode:** `?string` + +
+
+
+
+ +
diff --git a/src/CloudPDFClient.php b/src/CloudPDFClient.php index 17b75af..0cc5c2f 100644 --- a/src/CloudPDFClient.php +++ b/src/CloudPDFClient.php @@ -76,8 +76,8 @@ public function __construct( $defaultHeaders = [ 'X-Fern-Language' => 'PHP', 'X-Fern-SDK-Name' => 'CloudPDF', - 'X-Fern-SDK-Version' => '3.0.0-alpha.5', - 'User-Agent' => 'cloudpdf/sdk/3.0.0-alpha.5', + 'X-Fern-SDK-Version' => '3.0.0-alpha.6', + 'User-Agent' => 'cloudpdf/sdk/3.0.0-alpha.6', ]; if ($token != null) { $defaultHeaders['Authorization'] = "Bearer $token"; diff --git a/src/Documents/DocumentsClient.php b/src/Documents/DocumentsClient.php index fea55cc..b74a0e5 100644 --- a/src/Documents/DocumentsClient.php +++ b/src/Documents/DocumentsClient.php @@ -19,6 +19,8 @@ use CloudPDF\Types\DocumentsUploadProxy200Response; use CloudPDF\Core\Multipart\MultipartFormData; use CloudPDF\Core\Multipart\MultipartApiRequest; +use CloudPDF\Documents\Requests\DocumentsImportFromRequest; +use CloudPDF\Types\DocumentsImportFrom200Response; use CloudPDF\Documents\Requests\DocumentsInitRequest; use CloudPDF\Types\DocumentsInit200Response; @@ -448,6 +450,68 @@ public function uploadProxy(string $tenantId, string $id, UploadProxyDocumentsRe ); } + /** + * Default mode is synchronous and bounded: the response returns only after the transfer verified and committed (or failed). mode=async (connection sources only) answers 202 immediately and an in-process worker performs the transfer with leased, fenced retries; poll the document until ready/failed. The deployment import policy gates scheme, network range, and size; sources must declare a length. CloudPDF copies and owns the bytes — the source is never referenced in place. A 502 marks a retryable upstream failure: retry with the same idempotencyKey to resume the same document. URL sources are capabilities and never echoed back. Connection sources name operator-registered storage (bucket/prefix scope, allowed credential classes, and tenant bindings are deployment configuration); `revision` is provider-interpreted (S3 VersionId, GCS generation, Azure version id). + * + * Example: + * ```php + * $client->documents->importFrom( + * 'tenantId', + * new DocumentsImportFromRequest([ + * 'source' => DocumentsImportFromRequestSource::url(new DocumentsImportFromRequestSourceUrl([ + * 'url' => 'url', + * ])), + * ]), + * ); + * ``` + * + * @param string $tenantId + * @param DocumentsImportFromRequest $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return ?DocumentsImportFrom200Response + * @throws CloudPDFException + * @throws CloudPDFApiException + */ + public function importFrom(string $tenantId, DocumentsImportFromRequest $request, ?array $options = null): ?DocumentsImportFrom200Response + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "v1/tenants/{$tenantId}/documents/import", + method: HttpMethod::POST, + body: $request, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + if (empty($json)) { + return null; + } + return DocumentsImportFrom200Response::fromJson($json); + } + } catch (JsonException $e) { + throw new CloudPDFException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new CloudPDFException(message: $e->getMessage(), previous: $e); + } + throw new CloudPDFApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + /** * Example: * ```php diff --git a/src/Documents/Requests/DocumentsImportFromRequest.php b/src/Documents/Requests/DocumentsImportFromRequest.php new file mode 100644 index 0000000..dc48b40 --- /dev/null +++ b/src/Documents/Requests/DocumentsImportFromRequest.php @@ -0,0 +1,79 @@ + $metadata + */ + #[JsonProperty('metadata'), ArrayType(['string' => 'mixed'])] + public ?array $metadata; + + /** + * @var ?string $idempotencyKey + */ + #[JsonProperty('idempotencyKey')] + public ?string $idempotencyKey; + + /** + * @var ?value-of $dedupMode + */ + #[JsonProperty('dedupMode')] + public ?string $dedupMode; + + /** + * @var ?string $docId + */ + #[JsonProperty('docId')] + public ?string $docId; + + /** + * @var ?value-of $mode + */ + #[JsonProperty('mode')] + public ?string $mode; + + /** + * @param array{ + * source: DocumentsImportFromRequestSource, + * expected?: ?DocumentsImportFromRequestExpected, + * metadata?: ?array, + * idempotencyKey?: ?string, + * dedupMode?: ?value-of, + * docId?: ?string, + * mode?: ?value-of, + * } $values + */ + public function __construct( + array $values, + ) { + $this->source = $values['source']; + $this->expected = $values['expected'] ?? null; + $this->metadata = $values['metadata'] ?? null; + $this->idempotencyKey = $values['idempotencyKey'] ?? null; + $this->dedupMode = $values['dedupMode'] ?? null; + $this->docId = $values['docId'] ?? null; + $this->mode = $values['mode'] ?? null; + } +} diff --git a/src/Documents/Types/DocumentsImportFromRequestDedupMode.php b/src/Documents/Types/DocumentsImportFromRequestDedupMode.php new file mode 100644 index 0000000..10dd315 --- /dev/null +++ b/src/Documents/Types/DocumentsImportFromRequestDedupMode.php @@ -0,0 +1,9 @@ +sizeBytes = $values['sizeBytes'] ?? null; + $this->sha256 = $values['sha256'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Documents/Types/DocumentsImportFromRequestMode.php b/src/Documents/Types/DocumentsImportFromRequestMode.php new file mode 100644 index 0000000..e067617 --- /dev/null +++ b/src/Documents/Types/DocumentsImportFromRequestMode.php @@ -0,0 +1,9 @@ +kind = $values['kind']; + $this->value = $values['value']; + } + + /** + * @param DocumentsImportFromRequestSourceUrl $url + * @return DocumentsImportFromRequestSource + */ + public static function url(DocumentsImportFromRequestSourceUrl $url): DocumentsImportFromRequestSource + { + return new DocumentsImportFromRequestSource([ + 'kind' => 'url', + 'value' => $url, + ]); + } + + /** + * @param DocumentsImportFromRequestSourceConnection $connection + * @return DocumentsImportFromRequestSource + */ + public static function connection(DocumentsImportFromRequestSourceConnection $connection): DocumentsImportFromRequestSource + { + return new DocumentsImportFromRequestSource([ + 'kind' => 'connection', + 'value' => $connection, + ]); + } + + /** + * @return bool + */ + public function isUrl(): bool + { + return $this->value instanceof DocumentsImportFromRequestSourceUrl && $this->kind === 'url'; + } + + /** + * @return DocumentsImportFromRequestSourceUrl + */ + public function asUrl(): DocumentsImportFromRequestSourceUrl + { + if (!($this->value instanceof DocumentsImportFromRequestSourceUrl && $this->kind === 'url')) { + throw new Exception( + "Expected url; got " . $this->kind . " with value of type " . get_debug_type($this->value), + ); + } + + return $this->value; + } + + /** + * @return bool + */ + public function isConnection(): bool + { + return $this->value instanceof DocumentsImportFromRequestSourceConnection && $this->kind === 'connection'; + } + + /** + * @return DocumentsImportFromRequestSourceConnection + */ + public function asConnection(): DocumentsImportFromRequestSourceConnection + { + if (!($this->value instanceof DocumentsImportFromRequestSourceConnection && $this->kind === 'connection')) { + throw new Exception( + "Expected connection; got " . $this->kind . " with value of type " . get_debug_type($this->value), + ); + } + + return $this->value; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $result = []; + $result['kind'] = $this->kind; + + $base = parent::jsonSerialize(); + $result = array_merge($base, $result); + + switch ($this->kind) { + case 'url': + $value = $this->asUrl()->jsonSerialize(); + $result = array_merge($value, $result); + break; + case 'connection': + $value = $this->asConnection()->jsonSerialize(); + $result = array_merge($value, $result); + break; + case '_unknown': + default: + if (is_null($this->value)) { + break; + } + if ($this->value instanceof JsonSerializableType) { + $value = $this->value->jsonSerialize(); + $result = array_merge($value, $result); + } elseif (is_array($this->value)) { + $result = array_merge($this->value, $result); + } + } + + return $result; + } + + /** + * @param array $data + */ + public static function jsonDeserialize(array $data): static + { + $args = []; + if (!array_key_exists('kind', $data)) { + throw new Exception( + "JSON data is missing property 'kind'", + ); + } + $kind = $data['kind']; + if (!(is_string($kind))) { + throw new Exception( + "Expected property 'kind' in JSON data to be string, instead received " . get_debug_type($data['kind']), + ); + } + + $args['kind'] = $kind; + switch ($kind) { + case 'url': + $args['value'] = DocumentsImportFromRequestSourceUrl::jsonDeserialize($data); + break; + case 'connection': + $args['value'] = DocumentsImportFromRequestSourceConnection::jsonDeserialize($data); + break; + case '_unknown': + default: + $args['kind'] = '_unknown'; + $args['value'] = $data; + } + + // @phpstan-ignore-next-line + return new static($args); + } +} diff --git a/src/Documents/Types/DocumentsImportFromRequestSourceConnection.php b/src/Documents/Types/DocumentsImportFromRequestSourceConnection.php new file mode 100644 index 0000000..ddf4a91 --- /dev/null +++ b/src/Documents/Types/DocumentsImportFromRequestSourceConnection.php @@ -0,0 +1,50 @@ +connectionId = $values['connectionId']; + $this->key = $values['key']; + $this->revision = $values['revision'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Documents/Types/DocumentsImportFromRequestSourceUrl.php b/src/Documents/Types/DocumentsImportFromRequestSourceUrl.php new file mode 100644 index 0000000..b8c3992 --- /dev/null +++ b/src/Documents/Types/DocumentsImportFromRequestSourceUrl.php @@ -0,0 +1,34 @@ +url = $values['url']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom200Response.php b/src/Types/DocumentsImportFrom200Response.php new file mode 100644 index 0000000..518e11c --- /dev/null +++ b/src/Types/DocumentsImportFrom200Response.php @@ -0,0 +1,42 @@ + $tag + */ + #[JsonProperty('tag')] + public string $tag; + + /** + * @var DocumentsImportFrom200ResponseDocument $document + */ + #[JsonProperty('document')] + public DocumentsImportFrom200ResponseDocument $document; + + /** + * @param array{ + * tag: value-of, + * document: DocumentsImportFrom200ResponseDocument, + * } $values + */ + public function __construct( + array $values, + ) { + $this->tag = $values['tag']; + $this->document = $values['document']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom200ResponseDocument.php b/src/Types/DocumentsImportFrom200ResponseDocument.php new file mode 100644 index 0000000..3ab40b0 --- /dev/null +++ b/src/Types/DocumentsImportFrom200ResponseDocument.php @@ -0,0 +1,131 @@ + $state + */ + #[JsonProperty('state')] + public string $state; + + /** + * @var ?string $baseSha + */ + #[JsonProperty('baseSha')] + public ?string $baseSha; + + /** + * @var ?float $storageSizeBytes + */ + #[JsonProperty('storageSizeBytes')] + public ?float $storageSizeBytes; + + /** + * @var ?array $metadata + */ + #[JsonProperty('metadata'), ArrayType(['string' => 'mixed'])] + public ?array $metadata; + + /** + * @var ?string $idempotencyKey + */ + #[JsonProperty('idempotencyKey')] + public ?string $idempotencyKey; + + /** + * @var ?string $failureReason + */ + #[JsonProperty('failureReason')] + public ?string $failureReason; + + /** + * @var ?value-of $thumbnailState + */ + #[JsonProperty('thumbnailState')] + public ?string $thumbnailState; + + /** + * @var ?string $thumbnailUrl + */ + #[JsonProperty('thumbnailUrl')] + public ?string $thumbnailUrl; + + /** + * @var float $createdAt + */ + #[JsonProperty('createdAt')] + public float $createdAt; + + /** + * @var float $updatedAt + */ + #[JsonProperty('updatedAt')] + public float $updatedAt; + + /** + * @var ?string $createdBy + */ + #[JsonProperty('createdBy')] + public ?string $createdBy; + + /** + * @param array{ + * id: string, + * tenantId: string, + * state: value-of, + * createdAt: float, + * updatedAt: float, + * baseSha?: ?string, + * storageSizeBytes?: ?float, + * metadata?: ?array, + * idempotencyKey?: ?string, + * failureReason?: ?string, + * thumbnailState?: ?value-of, + * thumbnailUrl?: ?string, + * createdBy?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->id = $values['id']; + $this->tenantId = $values['tenantId']; + $this->state = $values['state']; + $this->baseSha = $values['baseSha'] ?? null; + $this->storageSizeBytes = $values['storageSizeBytes'] ?? null; + $this->metadata = $values['metadata'] ?? null; + $this->idempotencyKey = $values['idempotencyKey'] ?? null; + $this->failureReason = $values['failureReason'] ?? null; + $this->thumbnailState = $values['thumbnailState'] ?? null; + $this->thumbnailUrl = $values['thumbnailUrl'] ?? null; + $this->createdAt = $values['createdAt']; + $this->updatedAt = $values['updatedAt']; + $this->createdBy = $values['createdBy'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom200ResponseDocumentState.php b/src/Types/DocumentsImportFrom200ResponseDocumentState.php new file mode 100644 index 0000000..e735a3b --- /dev/null +++ b/src/Types/DocumentsImportFrom200ResponseDocumentState.php @@ -0,0 +1,11 @@ + $tag + */ + #[JsonProperty('tag')] + public string $tag; + + /** + * @var DocumentsImportFrom202ResponseDocument $document + */ + #[JsonProperty('document')] + public DocumentsImportFrom202ResponseDocument $document; + + /** + * @param array{ + * tag: value-of, + * document: DocumentsImportFrom202ResponseDocument, + * } $values + */ + public function __construct( + array $values, + ) { + $this->tag = $values['tag']; + $this->document = $values['document']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom202ResponseDocument.php b/src/Types/DocumentsImportFrom202ResponseDocument.php new file mode 100644 index 0000000..cf44704 --- /dev/null +++ b/src/Types/DocumentsImportFrom202ResponseDocument.php @@ -0,0 +1,131 @@ + $state + */ + #[JsonProperty('state')] + public string $state; + + /** + * @var ?string $baseSha + */ + #[JsonProperty('baseSha')] + public ?string $baseSha; + + /** + * @var ?float $storageSizeBytes + */ + #[JsonProperty('storageSizeBytes')] + public ?float $storageSizeBytes; + + /** + * @var ?array $metadata + */ + #[JsonProperty('metadata'), ArrayType(['string' => 'mixed'])] + public ?array $metadata; + + /** + * @var ?string $idempotencyKey + */ + #[JsonProperty('idempotencyKey')] + public ?string $idempotencyKey; + + /** + * @var ?string $failureReason + */ + #[JsonProperty('failureReason')] + public ?string $failureReason; + + /** + * @var ?value-of $thumbnailState + */ + #[JsonProperty('thumbnailState')] + public ?string $thumbnailState; + + /** + * @var ?string $thumbnailUrl + */ + #[JsonProperty('thumbnailUrl')] + public ?string $thumbnailUrl; + + /** + * @var float $createdAt + */ + #[JsonProperty('createdAt')] + public float $createdAt; + + /** + * @var float $updatedAt + */ + #[JsonProperty('updatedAt')] + public float $updatedAt; + + /** + * @var ?string $createdBy + */ + #[JsonProperty('createdBy')] + public ?string $createdBy; + + /** + * @param array{ + * id: string, + * tenantId: string, + * state: value-of, + * createdAt: float, + * updatedAt: float, + * baseSha?: ?string, + * storageSizeBytes?: ?float, + * metadata?: ?array, + * idempotencyKey?: ?string, + * failureReason?: ?string, + * thumbnailState?: ?value-of, + * thumbnailUrl?: ?string, + * createdBy?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->id = $values['id']; + $this->tenantId = $values['tenantId']; + $this->state = $values['state']; + $this->baseSha = $values['baseSha'] ?? null; + $this->storageSizeBytes = $values['storageSizeBytes'] ?? null; + $this->metadata = $values['metadata'] ?? null; + $this->idempotencyKey = $values['idempotencyKey'] ?? null; + $this->failureReason = $values['failureReason'] ?? null; + $this->thumbnailState = $values['thumbnailState'] ?? null; + $this->thumbnailUrl = $values['thumbnailUrl'] ?? null; + $this->createdAt = $values['createdAt']; + $this->updatedAt = $values['updatedAt']; + $this->createdBy = $values['createdBy'] ?? null; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom202ResponseDocumentState.php b/src/Types/DocumentsImportFrom202ResponseDocumentState.php new file mode 100644 index 0000000..08ce613 --- /dev/null +++ b/src/Types/DocumentsImportFrom202ResponseDocumentState.php @@ -0,0 +1,11 @@ +error = $values['error']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom400ResponseError.php b/src/Types/DocumentsImportFrom400ResponseError.php new file mode 100644 index 0000000..2c62d39 --- /dev/null +++ b/src/Types/DocumentsImportFrom400ResponseError.php @@ -0,0 +1,42 @@ +code = $values['code']; + $this->message = $values['message']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom403Response.php b/src/Types/DocumentsImportFrom403Response.php new file mode 100644 index 0000000..aa3856f --- /dev/null +++ b/src/Types/DocumentsImportFrom403Response.php @@ -0,0 +1,34 @@ +error = $values['error']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom403ResponseError.php b/src/Types/DocumentsImportFrom403ResponseError.php new file mode 100644 index 0000000..b6b75d8 --- /dev/null +++ b/src/Types/DocumentsImportFrom403ResponseError.php @@ -0,0 +1,42 @@ +code = $values['code']; + $this->message = $values['message']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom502Response.php b/src/Types/DocumentsImportFrom502Response.php new file mode 100644 index 0000000..f703489 --- /dev/null +++ b/src/Types/DocumentsImportFrom502Response.php @@ -0,0 +1,34 @@ +error = $values['error']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DocumentsImportFrom502ResponseError.php b/src/Types/DocumentsImportFrom502ResponseError.php new file mode 100644 index 0000000..13a43a3 --- /dev/null +++ b/src/Types/DocumentsImportFrom502ResponseError.php @@ -0,0 +1,42 @@ +code = $values['code']; + $this->message = $values['message']; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +}