From 575ab0ab9cd99d3cdd987452e436692710abbee6 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:09:56 +0000 Subject: [PATCH 1/3] [fern-generated] Update SDK Generated by Fern CLI Version: unknown Generators: - fernapi/fern-php-sdk: 2.1.5 --- .fern/metadata.json | 2 +- composer.json | 2 +- reference.md | 112 +++++++ src/Reporting/ReportingClient.php | 143 +++++++++ src/Reporting/Requests/LoadRequest.php | 98 +++++++ src/SquareClient.php | 11 +- src/Types/CacheMode.php | 11 + src/Types/Cube.php | 339 ++++++++++++++++++++++ src/Types/CubeJoin.php | 78 +++++ src/Types/CubeType.php | 9 + src/Types/CustomNumericFormat.php | 107 +++++++ src/Types/CustomTimeFormat.php | 81 ++++++ src/Types/Dimension.php | 386 +++++++++++++++++++++++++ src/Types/DimensionGranularity.php | 182 ++++++++++++ src/Types/DimensionOrder.php | 9 + src/Types/Folder.php | 79 +++++ src/Types/FormatDescription.php | 107 +++++++ src/Types/Hierarchy.php | 131 +++++++++ src/Types/JoinSubquery.php | 130 +++++++++ src/Types/LinkFormat.php | 81 ++++++ src/Types/LoadResponse.php | 131 +++++++++ src/Types/LoadResult.php | 174 +++++++++++ src/Types/LoadResultAnnotation.php | 131 +++++++++ src/Types/LoadResultDataColumnar.php | 82 ++++++ src/Types/LoadResultDataCompact.php | 82 ++++++ src/Types/Measure.php | 334 +++++++++++++++++++++ src/Types/MetadataResponse.php | 79 +++++ src/Types/NestedFolder.php | 79 +++++ src/Types/Query.php | 382 ++++++++++++++++++++++++ src/Types/QueryFilterAnd.php | 53 ++++ src/Types/QueryFilterCondition.php | 105 +++++++ src/Types/QueryFilterOr.php | 53 ++++ src/Types/ReportingError.php | 55 ++++ src/Types/ResponseFormat.php | 10 + src/Types/Segment.php | 157 ++++++++++ src/Types/SimpleFormat.php | 13 + src/Types/TimeDimension.php | 105 +++++++ 37 files changed, 4119 insertions(+), 4 deletions(-) create mode 100644 src/Reporting/ReportingClient.php create mode 100644 src/Reporting/Requests/LoadRequest.php create mode 100644 src/Types/CacheMode.php create mode 100644 src/Types/Cube.php create mode 100644 src/Types/CubeJoin.php create mode 100644 src/Types/CubeType.php create mode 100644 src/Types/CustomNumericFormat.php create mode 100644 src/Types/CustomTimeFormat.php create mode 100644 src/Types/Dimension.php create mode 100644 src/Types/DimensionGranularity.php create mode 100644 src/Types/DimensionOrder.php create mode 100644 src/Types/Folder.php create mode 100644 src/Types/FormatDescription.php create mode 100644 src/Types/Hierarchy.php create mode 100644 src/Types/JoinSubquery.php create mode 100644 src/Types/LinkFormat.php create mode 100644 src/Types/LoadResponse.php create mode 100644 src/Types/LoadResult.php create mode 100644 src/Types/LoadResultAnnotation.php create mode 100644 src/Types/LoadResultDataColumnar.php create mode 100644 src/Types/LoadResultDataCompact.php create mode 100644 src/Types/Measure.php create mode 100644 src/Types/MetadataResponse.php create mode 100644 src/Types/NestedFolder.php create mode 100644 src/Types/Query.php create mode 100644 src/Types/QueryFilterAnd.php create mode 100644 src/Types/QueryFilterCondition.php create mode 100644 src/Types/QueryFilterOr.php create mode 100644 src/Types/ReportingError.php create mode 100644 src/Types/ResponseFormat.php create mode 100644 src/Types/Segment.php create mode 100644 src/Types/SimpleFormat.php create mode 100644 src/Types/TimeDimension.php diff --git a/.fern/metadata.json b/.fern/metadata.json index dc2de208..19eebcb0 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -23,5 +23,5 @@ } } }, - "sdkVersion": "45.1.0.20260520" + "sdkVersion": "45.2.0.20260520" } \ No newline at end of file diff --git a/composer.json b/composer.json index 8846c865..31d6e3d3 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "square/square", - "version": "45.1.0.20260520", + "version": "45.2.0.20260520", "description": "Use Square APIs to manage and run business including payment, customer, product, inventory, and employee management.", "keywords": [ "square", diff --git a/reference.md b/reference.md index cfbcd0aa..35f1d2b1 100644 --- a/reference.md +++ b/reference.md @@ -16007,6 +16007,118 @@ $client->vendors->update( + + + + +## Reporting +
$client->reporting->getMetadata() -> MetadataResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->reporting->getMetadata(); +``` +
+
+
+
+ + +
+
+
+ +
$client->reporting->load($request) -> LoadResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```php +$client->reporting->load( + new LoadRequest([]), +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**$queryType:** `?string` + +
+
+ +
+
+ +**$cache:** `?string` + +
+
+ +
+
+ +**$query:** `?Query` + +
+
+
+
+ +
diff --git a/src/Reporting/ReportingClient.php b/src/Reporting/ReportingClient.php new file mode 100644 index 00000000..68e42416 --- /dev/null +++ b/src/Reporting/ReportingClient.php @@ -0,0 +1,143 @@ +, + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator + */ + private array $options; + + /** + * @var RawClient $client + */ + private RawClient $client; + + /** + * @param RawClient $client + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * } $options + */ + public function __construct( + RawClient $client, + ?array $options = null, + ) { + $this->client = $client; + $this->options = $options ?? []; + } + + /** + * Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`. + * + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return MetadataResponse + * @throws SquareException + * @throws SquareApiException + */ + public function getMetadata(?array $options = null): MetadataResponse + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Production->value, + path: "reporting/v1/meta", + method: HttpMethod::GET, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + return MetadataResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SquareException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SquareException(message: $e->getMessage(), previous: $e); + } + throw new SquareApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready. + * + * @param LoadRequest $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return LoadResponse + * @throws SquareException + * @throws SquareApiException + */ + public function load(LoadRequest $request = new LoadRequest(), ?array $options = null): LoadResponse + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? Environments::Production->value, + path: "reporting/v1/load", + method: HttpMethod::POST, + body: $request, + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + return LoadResponse::fromJson($json); + } + } catch (JsonException $e) { + throw new SquareException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SquareException(message: $e->getMessage(), previous: $e); + } + throw new SquareApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } +} diff --git a/src/Reporting/Requests/LoadRequest.php b/src/Reporting/Requests/LoadRequest.php new file mode 100644 index 00000000..e3111b84 --- /dev/null +++ b/src/Reporting/Requests/LoadRequest.php @@ -0,0 +1,98 @@ + $cache + */ + #[JsonProperty('cache')] + private ?string $cache; + + /** + * @var ?Query $query + */ + #[JsonProperty('query')] + private ?Query $query; + + /** + * @param array{ + * queryType?: ?string, + * cache?: ?value-of, + * query?: ?Query, + * } $values + */ + public function __construct( + array $values = [], + ) { + $this->queryType = $values['queryType'] ?? null; + $this->cache = $values['cache'] ?? null; + $this->query = $values['query'] ?? null; + } + + /** + * @return ?string + */ + public function getQueryType(): ?string + { + return $this->queryType; + } + + /** + * @param ?string $value + */ + public function setQueryType(?string $value = null): self + { + $this->queryType = $value; + $this->_setField('queryType'); + return $this; + } + + /** + * @return ?value-of + */ + public function getCache(): ?string + { + return $this->cache; + } + + /** + * @param ?value-of $value + */ + public function setCache(?string $value = null): self + { + $this->cache = $value; + $this->_setField('cache'); + return $this; + } + + /** + * @return ?Query + */ + public function getQuery(): ?Query + { + return $this->query; + } + + /** + * @param ?Query $value + */ + public function setQuery(?Query $value = null): self + { + $this->query = $value; + $this->_setField('query'); + return $this; + } +} diff --git a/src/SquareClient.php b/src/SquareClient.php index d009ffdf..08ee8d59 100644 --- a/src/SquareClient.php +++ b/src/SquareClient.php @@ -35,6 +35,7 @@ use Square\Terminal\TerminalClient; use Square\TransferOrders\TransferOrdersClient; use Square\Vendors\VendorsClient; +use Square\Reporting\ReportingClient; use Square\CashDrawers\CashDrawersClient; use Square\Webhooks\WebhooksClient; use Psr\Http\Client\ClientInterface; @@ -208,6 +209,11 @@ class SquareClient */ public VendorsClient $vendors; + /** + * @var ReportingClient $reporting + */ + public ReportingClient $reporting; + /** * @var CashDrawersClient $cashDrawers */ @@ -256,8 +262,8 @@ public function __construct( 'Square-Version' => '2026-05-20', 'X-Fern-Language' => 'PHP', 'X-Fern-SDK-Name' => 'Square', - 'X-Fern-SDK-Version' => '45.1.0.20260520', - 'User-Agent' => 'square/square/45.1.0.20260520', + 'X-Fern-SDK-Version' => '45.2.0.20260520', + 'User-Agent' => 'square/square/45.2.0.20260520', ]; if ($version != null) { $defaultHeaders['Square-Version'] = $version; @@ -307,6 +313,7 @@ public function __construct( $this->terminal = new TerminalClient($this->client, $this->options); $this->transferOrders = new TransferOrdersClient($this->client, $this->options); $this->vendors = new VendorsClient($this->client, $this->options); + $this->reporting = new ReportingClient($this->client, $this->options); $this->cashDrawers = new CashDrawersClient($this->client, $this->options); $this->webhooks = new WebhooksClient($this->client, $this->options); } diff --git a/src/Types/CacheMode.php b/src/Types/CacheMode.php new file mode 100644 index 00000000..13da5c6e --- /dev/null +++ b/src/Types/CacheMode.php @@ -0,0 +1,11 @@ + $type + */ + #[JsonProperty('type')] + private string $type; + + /** + * @var ?array $meta + */ + #[JsonProperty('meta'), ArrayType(['string' => 'mixed'])] + private ?array $meta; + + /** + * @var ?string $description + */ + #[JsonProperty('description')] + private ?string $description; + + /** + * @var array $measures + */ + #[JsonProperty('measures'), ArrayType([Measure::class])] + private array $measures; + + /** + * @var array $dimensions + */ + #[JsonProperty('dimensions'), ArrayType([Dimension::class])] + private array $dimensions; + + /** + * @var array $segments + */ + #[JsonProperty('segments'), ArrayType([Segment::class])] + private array $segments; + + /** + * @var ?array $joins + */ + #[JsonProperty('joins'), ArrayType([CubeJoin::class])] + private ?array $joins; + + /** + * @var ?array $folders + */ + #[JsonProperty('folders'), ArrayType([Folder::class])] + private ?array $folders; + + /** + * @var ?array $nestedFolders + */ + #[JsonProperty('nestedFolders'), ArrayType([NestedFolder::class])] + private ?array $nestedFolders; + + /** + * @var ?array $hierarchies + */ + #[JsonProperty('hierarchies'), ArrayType([Hierarchy::class])] + private ?array $hierarchies; + + /** + * @param array{ + * name: string, + * type: value-of, + * measures: array, + * dimensions: array, + * segments: array, + * title?: ?string, + * meta?: ?array, + * description?: ?string, + * joins?: ?array, + * folders?: ?array, + * nestedFolders?: ?array, + * hierarchies?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->title = $values['title'] ?? null; + $this->type = $values['type']; + $this->meta = $values['meta'] ?? null; + $this->description = $values['description'] ?? null; + $this->measures = $values['measures']; + $this->dimensions = $values['dimensions']; + $this->segments = $values['segments']; + $this->joins = $values['joins'] ?? null; + $this->folders = $values['folders'] ?? null; + $this->nestedFolders = $values['nestedFolders'] ?? null; + $this->hierarchies = $values['hierarchies'] ?? null; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return ?string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param ?string $value + */ + public function setTitle(?string $value = null): self + { + $this->title = $value; + $this->_setField('title'); + return $this; + } + + /** + * @return value-of + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param value-of $value + */ + public function setType(string $value): self + { + $this->type = $value; + $this->_setField('type'); + return $this; + } + + /** + * @return ?array + */ + public function getMeta(): ?array + { + return $this->meta; + } + + /** + * @param ?array $value + */ + public function setMeta(?array $value = null): self + { + $this->meta = $value; + $this->_setField('meta'); + return $this; + } + + /** + * @return ?string + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param ?string $value + */ + public function setDescription(?string $value = null): self + { + $this->description = $value; + $this->_setField('description'); + return $this; + } + + /** + * @return array + */ + public function getMeasures(): array + { + return $this->measures; + } + + /** + * @param array $value + */ + public function setMeasures(array $value): self + { + $this->measures = $value; + $this->_setField('measures'); + return $this; + } + + /** + * @return array + */ + public function getDimensions(): array + { + return $this->dimensions; + } + + /** + * @param array $value + */ + public function setDimensions(array $value): self + { + $this->dimensions = $value; + $this->_setField('dimensions'); + return $this; + } + + /** + * @return array + */ + public function getSegments(): array + { + return $this->segments; + } + + /** + * @param array $value + */ + public function setSegments(array $value): self + { + $this->segments = $value; + $this->_setField('segments'); + return $this; + } + + /** + * @return ?array + */ + public function getJoins(): ?array + { + return $this->joins; + } + + /** + * @param ?array $value + */ + public function setJoins(?array $value = null): self + { + $this->joins = $value; + $this->_setField('joins'); + return $this; + } + + /** + * @return ?array + */ + public function getFolders(): ?array + { + return $this->folders; + } + + /** + * @param ?array $value + */ + public function setFolders(?array $value = null): self + { + $this->folders = $value; + $this->_setField('folders'); + return $this; + } + + /** + * @return ?array + */ + public function getNestedFolders(): ?array + { + return $this->nestedFolders; + } + + /** + * @param ?array $value + */ + public function setNestedFolders(?array $value = null): self + { + $this->nestedFolders = $value; + $this->_setField('nestedFolders'); + return $this; + } + + /** + * @return ?array + */ + public function getHierarchies(): ?array + { + return $this->hierarchies; + } + + /** + * @param ?array $value + */ + public function setHierarchies(?array $value = null): self + { + $this->hierarchies = $value; + $this->_setField('hierarchies'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/CubeJoin.php b/src/Types/CubeJoin.php new file mode 100644 index 00000000..b26087e6 --- /dev/null +++ b/src/Types/CubeJoin.php @@ -0,0 +1,78 @@ +name = $values['name']; + $this->relationship = $values['relationship']; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return string + */ + public function getRelationship(): string + { + return $this->relationship; + } + + /** + * @param string $value + */ + public function setRelationship(string $value): self + { + $this->relationship = $value; + $this->_setField('relationship'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/CubeType.php b/src/Types/CubeType.php new file mode 100644 index 00000000..d312102b --- /dev/null +++ b/src/Types/CubeType.php @@ -0,0 +1,9 @@ +type = $values['type']; + $this->value = $values['value']; + $this->alias = $values['alias'] ?? null; + } + + /** + * @return 'custom-numeric' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param 'custom-numeric' $value + */ + public function setType(string $value): self + { + $this->type = $value; + $this->_setField('type'); + return $this; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @param string $value + */ + public function setValue(string $value): self + { + $this->value = $value; + $this->_setField('value'); + return $this; + } + + /** + * @return ?string + */ + public function getAlias(): ?string + { + return $this->alias; + } + + /** + * @param ?string $value + */ + public function setAlias(?string $value = null): self + { + $this->alias = $value; + $this->_setField('alias'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/CustomTimeFormat.php b/src/Types/CustomTimeFormat.php new file mode 100644 index 00000000..f42bf91c --- /dev/null +++ b/src/Types/CustomTimeFormat.php @@ -0,0 +1,81 @@ +type = $values['type']; + $this->value = $values['value']; + } + + /** + * @return 'custom-time' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param 'custom-time' $value + */ + public function setType(string $value): self + { + $this->type = $value; + $this->_setField('type'); + return $this; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @param string $value + */ + public function setValue(string $value): self + { + $this->value = $value; + $this->_setField('value'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/Dimension.php b/src/Types/Dimension.php new file mode 100644 index 00000000..6c0b51a6 --- /dev/null +++ b/src/Types/Dimension.php @@ -0,0 +1,386 @@ + $granularities + */ + #[JsonProperty('granularities'), ArrayType([DimensionGranularity::class])] + private ?array $granularities; + + /** + * @var ?array $meta + */ + #[JsonProperty('meta'), ArrayType(['string' => 'mixed'])] + private ?array $meta; + + /** + * @var ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null $format + */ + #[JsonProperty('format'), Union('string', LinkFormat::class, CustomTimeFormat::class, CustomNumericFormat::class, 'null')] + private string|LinkFormat|CustomTimeFormat|CustomNumericFormat|null $format; + + /** + * @var ?FormatDescription $formatDescription + */ + #[JsonProperty('formatDescription')] + private ?FormatDescription $formatDescription; + + /** + * @var ?string $currency ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR) + */ + #[JsonProperty('currency')] + private ?string $currency; + + /** + * @var ?value-of $order + */ + #[JsonProperty('order')] + private ?string $order; + + /** + * @var ?string $key Key reference for the dimension + */ + #[JsonProperty('key')] + private ?string $key; + + /** + * @param array{ + * name: string, + * type: string, + * title?: ?string, + * shortTitle?: ?string, + * description?: ?string, + * aliasMember?: ?string, + * granularities?: ?array, + * meta?: ?array, + * format?: ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null, + * formatDescription?: ?FormatDescription, + * currency?: ?string, + * order?: ?value-of, + * key?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->title = $values['title'] ?? null; + $this->shortTitle = $values['shortTitle'] ?? null; + $this->description = $values['description'] ?? null; + $this->type = $values['type']; + $this->aliasMember = $values['aliasMember'] ?? null; + $this->granularities = $values['granularities'] ?? null; + $this->meta = $values['meta'] ?? null; + $this->format = $values['format'] ?? null; + $this->formatDescription = $values['formatDescription'] ?? null; + $this->currency = $values['currency'] ?? null; + $this->order = $values['order'] ?? null; + $this->key = $values['key'] ?? null; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return ?string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param ?string $value + */ + public function setTitle(?string $value = null): self + { + $this->title = $value; + $this->_setField('title'); + return $this; + } + + /** + * @return ?string + */ + public function getShortTitle(): ?string + { + return $this->shortTitle; + } + + /** + * @param ?string $value + */ + public function setShortTitle(?string $value = null): self + { + $this->shortTitle = $value; + $this->_setField('shortTitle'); + return $this; + } + + /** + * @return ?string + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param ?string $value + */ + public function setDescription(?string $value = null): self + { + $this->description = $value; + $this->_setField('description'); + return $this; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param string $value + */ + public function setType(string $value): self + { + $this->type = $value; + $this->_setField('type'); + return $this; + } + + /** + * @return ?string + */ + public function getAliasMember(): ?string + { + return $this->aliasMember; + } + + /** + * @param ?string $value + */ + public function setAliasMember(?string $value = null): self + { + $this->aliasMember = $value; + $this->_setField('aliasMember'); + return $this; + } + + /** + * @return ?array + */ + public function getGranularities(): ?array + { + return $this->granularities; + } + + /** + * @param ?array $value + */ + public function setGranularities(?array $value = null): self + { + $this->granularities = $value; + $this->_setField('granularities'); + return $this; + } + + /** + * @return ?array + */ + public function getMeta(): ?array + { + return $this->meta; + } + + /** + * @param ?array $value + */ + public function setMeta(?array $value = null): self + { + $this->meta = $value; + $this->_setField('meta'); + return $this; + } + + /** + * @return ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null + */ + public function getFormat(): string|LinkFormat|CustomTimeFormat|CustomNumericFormat|null + { + return $this->format; + } + + /** + * @param ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null $value + */ + public function setFormat(string|LinkFormat|CustomTimeFormat|CustomNumericFormat|null $value = null): self + { + $this->format = $value; + $this->_setField('format'); + return $this; + } + + /** + * @return ?FormatDescription + */ + public function getFormatDescription(): ?FormatDescription + { + return $this->formatDescription; + } + + /** + * @param ?FormatDescription $value + */ + public function setFormatDescription(?FormatDescription $value = null): self + { + $this->formatDescription = $value; + $this->_setField('formatDescription'); + return $this; + } + + /** + * @return ?string + */ + public function getCurrency(): ?string + { + return $this->currency; + } + + /** + * @param ?string $value + */ + public function setCurrency(?string $value = null): self + { + $this->currency = $value; + $this->_setField('currency'); + return $this; + } + + /** + * @return ?value-of + */ + public function getOrder(): ?string + { + return $this->order; + } + + /** + * @param ?value-of $value + */ + public function setOrder(?string $value = null): self + { + $this->order = $value; + $this->_setField('order'); + return $this; + } + + /** + * @return ?string + */ + public function getKey(): ?string + { + return $this->key; + } + + /** + * @param ?string $value + */ + public function setKey(?string $value = null): self + { + $this->key = $value; + $this->_setField('key'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DimensionGranularity.php b/src/Types/DimensionGranularity.php new file mode 100644 index 00000000..1750e357 --- /dev/null +++ b/src/Types/DimensionGranularity.php @@ -0,0 +1,182 @@ +name = $values['name']; + $this->title = $values['title']; + $this->interval = $values['interval'] ?? null; + $this->sql = $values['sql'] ?? null; + $this->offset = $values['offset'] ?? null; + $this->origin = $values['origin'] ?? null; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $value + */ + public function setTitle(string $value): self + { + $this->title = $value; + $this->_setField('title'); + return $this; + } + + /** + * @return ?string + */ + public function getInterval(): ?string + { + return $this->interval; + } + + /** + * @param ?string $value + */ + public function setInterval(?string $value = null): self + { + $this->interval = $value; + $this->_setField('interval'); + return $this; + } + + /** + * @return ?string + */ + public function getSql(): ?string + { + return $this->sql; + } + + /** + * @param ?string $value + */ + public function setSql(?string $value = null): self + { + $this->sql = $value; + $this->_setField('sql'); + return $this; + } + + /** + * @return ?string + */ + public function getOffset(): ?string + { + return $this->offset; + } + + /** + * @param ?string $value + */ + public function setOffset(?string $value = null): self + { + $this->offset = $value; + $this->_setField('offset'); + return $this; + } + + /** + * @return ?string + */ + public function getOrigin(): ?string + { + return $this->origin; + } + + /** + * @param ?string $value + */ + public function setOrigin(?string $value = null): self + { + $this->origin = $value; + $this->_setField('origin'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/DimensionOrder.php b/src/Types/DimensionOrder.php new file mode 100644 index 00000000..9d71e527 --- /dev/null +++ b/src/Types/DimensionOrder.php @@ -0,0 +1,9 @@ + $members + */ + #[JsonProperty('members'), ArrayType(['string'])] + private array $members; + + /** + * @param array{ + * name: string, + * members: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->members = $values['members']; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return array + */ + public function getMembers(): array + { + return $this->members; + } + + /** + * @param array $value + */ + public function setMembers(array $value): self + { + $this->members = $value; + $this->_setField('members'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/FormatDescription.php b/src/Types/FormatDescription.php new file mode 100644 index 00000000..30a8b386 --- /dev/null +++ b/src/Types/FormatDescription.php @@ -0,0 +1,107 @@ +name = $values['name']; + $this->specifier = $values['specifier']; + $this->currency = $values['currency'] ?? null; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return string + */ + public function getSpecifier(): string + { + return $this->specifier; + } + + /** + * @param string $value + */ + public function setSpecifier(string $value): self + { + $this->specifier = $value; + $this->_setField('specifier'); + return $this; + } + + /** + * @return ?string + */ + public function getCurrency(): ?string + { + return $this->currency; + } + + /** + * @param ?string $value + */ + public function setCurrency(?string $value = null): self + { + $this->currency = $value; + $this->_setField('currency'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/Hierarchy.php b/src/Types/Hierarchy.php new file mode 100644 index 00000000..39431630 --- /dev/null +++ b/src/Types/Hierarchy.php @@ -0,0 +1,131 @@ + $levels + */ + #[JsonProperty('levels'), ArrayType(['string'])] + private array $levels; + + /** + * @param array{ + * name: string, + * levels: array, + * aliasMember?: ?string, + * title?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->aliasMember = $values['aliasMember'] ?? null; + $this->title = $values['title'] ?? null; + $this->levels = $values['levels']; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return ?string + */ + public function getAliasMember(): ?string + { + return $this->aliasMember; + } + + /** + * @param ?string $value + */ + public function setAliasMember(?string $value = null): self + { + $this->aliasMember = $value; + $this->_setField('aliasMember'); + return $this; + } + + /** + * @return ?string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param ?string $value + */ + public function setTitle(?string $value = null): self + { + $this->title = $value; + $this->_setField('title'); + return $this; + } + + /** + * @return array + */ + public function getLevels(): array + { + return $this->levels; + } + + /** + * @param array $value + */ + public function setLevels(array $value): self + { + $this->levels = $value; + $this->_setField('levels'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/JoinSubquery.php b/src/Types/JoinSubquery.php new file mode 100644 index 00000000..2673a3d5 --- /dev/null +++ b/src/Types/JoinSubquery.php @@ -0,0 +1,130 @@ +sql = $values['sql']; + $this->on = $values['on']; + $this->joinType = $values['joinType']; + $this->alias = $values['alias']; + } + + /** + * @return string + */ + public function getSql(): string + { + return $this->sql; + } + + /** + * @param string $value + */ + public function setSql(string $value): self + { + $this->sql = $value; + $this->_setField('sql'); + return $this; + } + + /** + * @return string + */ + public function getOn(): string + { + return $this->on; + } + + /** + * @param string $value + */ + public function setOn(string $value): self + { + $this->on = $value; + $this->_setField('on'); + return $this; + } + + /** + * @return string + */ + public function getJoinType(): string + { + return $this->joinType; + } + + /** + * @param string $value + */ + public function setJoinType(string $value): self + { + $this->joinType = $value; + $this->_setField('joinType'); + return $this; + } + + /** + * @return string + */ + public function getAlias(): string + { + return $this->alias; + } + + /** + * @param string $value + */ + public function setAlias(string $value): self + { + $this->alias = $value; + $this->_setField('alias'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/LinkFormat.php b/src/Types/LinkFormat.php new file mode 100644 index 00000000..474e8772 --- /dev/null +++ b/src/Types/LinkFormat.php @@ -0,0 +1,81 @@ +label = $values['label']; + $this->type = $values['type']; + } + + /** + * @return string + */ + public function getLabel(): string + { + return $this->label; + } + + /** + * @param string $value + */ + public function setLabel(string $value): self + { + $this->label = $value; + $this->_setField('label'); + return $this; + } + + /** + * @return 'link' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param 'link' $value + */ + public function setType(string $value): self + { + $this->type = $value; + $this->_setField('type'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/LoadResponse.php b/src/Types/LoadResponse.php new file mode 100644 index 00000000..e2dba995 --- /dev/null +++ b/src/Types/LoadResponse.php @@ -0,0 +1,131 @@ + $pivotQuery + */ + #[JsonProperty('pivotQuery'), ArrayType(['string' => 'mixed'])] + private ?array $pivotQuery; + + /** + * @var ?bool $slowQuery + */ + #[JsonProperty('slowQuery')] + private ?bool $slowQuery; + + /** + * @var ?string $queryType + */ + #[JsonProperty('queryType')] + private ?string $queryType; + + /** + * @var array $results + */ + #[JsonProperty('results'), ArrayType([LoadResult::class])] + private array $results; + + /** + * @param array{ + * results: array, + * pivotQuery?: ?array, + * slowQuery?: ?bool, + * queryType?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->pivotQuery = $values['pivotQuery'] ?? null; + $this->slowQuery = $values['slowQuery'] ?? null; + $this->queryType = $values['queryType'] ?? null; + $this->results = $values['results']; + } + + /** + * @return ?array + */ + public function getPivotQuery(): ?array + { + return $this->pivotQuery; + } + + /** + * @param ?array $value + */ + public function setPivotQuery(?array $value = null): self + { + $this->pivotQuery = $value; + $this->_setField('pivotQuery'); + return $this; + } + + /** + * @return ?bool + */ + public function getSlowQuery(): ?bool + { + return $this->slowQuery; + } + + /** + * @param ?bool $value + */ + public function setSlowQuery(?bool $value = null): self + { + $this->slowQuery = $value; + $this->_setField('slowQuery'); + return $this; + } + + /** + * @return ?string + */ + public function getQueryType(): ?string + { + return $this->queryType; + } + + /** + * @param ?string $value + */ + public function setQueryType(?string $value = null): self + { + $this->queryType = $value; + $this->_setField('queryType'); + return $this; + } + + /** + * @return array + */ + public function getResults(): array + { + return $this->results; + } + + /** + * @param array $value + */ + public function setResults(array $value): self + { + $this->results = $value; + $this->_setField('results'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/LoadResult.php b/src/Types/LoadResult.php new file mode 100644 index 00000000..d0b01bce --- /dev/null +++ b/src/Types/LoadResult.php @@ -0,0 +1,174 @@ +> + * |LoadResultDataCompact + * |LoadResultDataColumnar + * ) $data + */ + #[JsonProperty('data'), Union([['string' => 'mixed']], LoadResultDataCompact::class, LoadResultDataColumnar::class)] + private array|LoadResultDataCompact|LoadResultDataColumnar $data; + + /** + * @var ?array> $refreshKeyValues + */ + #[JsonProperty('refreshKeyValues'), ArrayType([['string' => 'mixed']])] + private ?array $refreshKeyValues; + + /** + * @var ?string $lastRefreshTime + */ + #[JsonProperty('lastRefreshTime')] + private ?string $lastRefreshTime; + + /** + * @param array{ + * annotation: LoadResultAnnotation, + * data: ( + * array> + * |LoadResultDataCompact + * |LoadResultDataColumnar + * ), + * dataSource?: ?string, + * refreshKeyValues?: ?array>, + * lastRefreshTime?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->dataSource = $values['dataSource'] ?? null; + $this->annotation = $values['annotation']; + $this->data = $values['data']; + $this->refreshKeyValues = $values['refreshKeyValues'] ?? null; + $this->lastRefreshTime = $values['lastRefreshTime'] ?? null; + } + + /** + * @return ?string + */ + public function getDataSource(): ?string + { + return $this->dataSource; + } + + /** + * @param ?string $value + */ + public function setDataSource(?string $value = null): self + { + $this->dataSource = $value; + $this->_setField('dataSource'); + return $this; + } + + /** + * @return LoadResultAnnotation + */ + public function getAnnotation(): LoadResultAnnotation + { + return $this->annotation; + } + + /** + * @param LoadResultAnnotation $value + */ + public function setAnnotation(LoadResultAnnotation $value): self + { + $this->annotation = $value; + $this->_setField('annotation'); + return $this; + } + + /** + * @return ( + * array> + * |LoadResultDataCompact + * |LoadResultDataColumnar + * ) + */ + public function getData(): array|LoadResultDataCompact|LoadResultDataColumnar + { + return $this->data; + } + + /** + * @param ( + * array> + * |LoadResultDataCompact + * |LoadResultDataColumnar + * ) $value + */ + public function setData(array|LoadResultDataCompact|LoadResultDataColumnar $value): self + { + $this->data = $value; + $this->_setField('data'); + return $this; + } + + /** + * @return ?array> + */ + public function getRefreshKeyValues(): ?array + { + return $this->refreshKeyValues; + } + + /** + * @param ?array> $value + */ + public function setRefreshKeyValues(?array $value = null): self + { + $this->refreshKeyValues = $value; + $this->_setField('refreshKeyValues'); + return $this; + } + + /** + * @return ?string + */ + public function getLastRefreshTime(): ?string + { + return $this->lastRefreshTime; + } + + /** + * @param ?string $value + */ + public function setLastRefreshTime(?string $value = null): self + { + $this->lastRefreshTime = $value; + $this->_setField('lastRefreshTime'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/LoadResultAnnotation.php b/src/Types/LoadResultAnnotation.php new file mode 100644 index 00000000..4ab5b7af --- /dev/null +++ b/src/Types/LoadResultAnnotation.php @@ -0,0 +1,131 @@ + $measures + */ + #[JsonProperty('measures'), ArrayType(['string' => 'mixed'])] + private array $measures; + + /** + * @var array $dimensions + */ + #[JsonProperty('dimensions'), ArrayType(['string' => 'mixed'])] + private array $dimensions; + + /** + * @var array $segments + */ + #[JsonProperty('segments'), ArrayType(['string' => 'mixed'])] + private array $segments; + + /** + * @var array $timeDimensions + */ + #[JsonProperty('timeDimensions'), ArrayType(['string' => 'mixed'])] + private array $timeDimensions; + + /** + * @param array{ + * measures: array, + * dimensions: array, + * segments: array, + * timeDimensions: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->measures = $values['measures']; + $this->dimensions = $values['dimensions']; + $this->segments = $values['segments']; + $this->timeDimensions = $values['timeDimensions']; + } + + /** + * @return array + */ + public function getMeasures(): array + { + return $this->measures; + } + + /** + * @param array $value + */ + public function setMeasures(array $value): self + { + $this->measures = $value; + $this->_setField('measures'); + return $this; + } + + /** + * @return array + */ + public function getDimensions(): array + { + return $this->dimensions; + } + + /** + * @param array $value + */ + public function setDimensions(array $value): self + { + $this->dimensions = $value; + $this->_setField('dimensions'); + return $this; + } + + /** + * @return array + */ + public function getSegments(): array + { + return $this->segments; + } + + /** + * @param array $value + */ + public function setSegments(array $value): self + { + $this->segments = $value; + $this->_setField('segments'); + return $this; + } + + /** + * @return array + */ + public function getTimeDimensions(): array + { + return $this->timeDimensions; + } + + /** + * @param array $value + */ + public function setTimeDimensions(array $value): self + { + $this->timeDimensions = $value; + $this->_setField('timeDimensions'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/LoadResultDataColumnar.php b/src/Types/LoadResultDataColumnar.php new file mode 100644 index 00000000..0ff87adb --- /dev/null +++ b/src/Types/LoadResultDataColumnar.php @@ -0,0 +1,82 @@ + $members Ordered list of member names. Element `i` of `columns` holds the values for `members[i]` across all rows. + */ + #[JsonProperty('members'), ArrayType(['string'])] + private array $members; + + /** + * @var array> $columns One array per member, in the same order as `members`. Each inner array contains the primitive value of that member for every row (null, boolean, number, string). + */ + #[JsonProperty('columns'), ArrayType([['mixed']])] + private array $columns; + + /** + * @param array{ + * members: array, + * columns: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->members = $values['members']; + $this->columns = $values['columns']; + } + + /** + * @return array + */ + public function getMembers(): array + { + return $this->members; + } + + /** + * @param array $value + */ + public function setMembers(array $value): self + { + $this->members = $value; + $this->_setField('members'); + return $this; + } + + /** + * @return array> + */ + public function getColumns(): array + { + return $this->columns; + } + + /** + * @param array> $value + */ + public function setColumns(array $value): self + { + $this->columns = $value; + $this->_setField('columns'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/LoadResultDataCompact.php b/src/Types/LoadResultDataCompact.php new file mode 100644 index 00000000..ac921508 --- /dev/null +++ b/src/Types/LoadResultDataCompact.php @@ -0,0 +1,82 @@ + $members Ordered list of member names that correspond to each cell position in `dataset` rows. + */ + #[JsonProperty('members'), ArrayType(['string'])] + private array $members; + + /** + * @var array> $dataset Array of rows, where each row is an array of primitive values (null, boolean, number, string) aligned with `members`. + */ + #[JsonProperty('dataset'), ArrayType([['mixed']])] + private array $dataset; + + /** + * @param array{ + * members: array, + * dataset: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->members = $values['members']; + $this->dataset = $values['dataset']; + } + + /** + * @return array + */ + public function getMembers(): array + { + return $this->members; + } + + /** + * @param array $value + */ + public function setMembers(array $value): self + { + $this->members = $value; + $this->_setField('members'); + return $this; + } + + /** + * @return array> + */ + public function getDataset(): array + { + return $this->dataset; + } + + /** + * @param array> $value + */ + public function setDataset(array $value): self + { + $this->dataset = $value; + $this->_setField('dataset'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/Measure.php b/src/Types/Measure.php new file mode 100644 index 00000000..5a7b0a73 --- /dev/null +++ b/src/Types/Measure.php @@ -0,0 +1,334 @@ + $meta + */ + #[JsonProperty('meta'), ArrayType(['string' => 'mixed'])] + private ?array $meta; + + /** + * @var ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null $format + */ + #[JsonProperty('format'), Union('string', LinkFormat::class, CustomTimeFormat::class, CustomNumericFormat::class, 'null')] + private string|LinkFormat|CustomTimeFormat|CustomNumericFormat|null $format; + + /** + * @var ?FormatDescription $formatDescription + */ + #[JsonProperty('formatDescription')] + private ?FormatDescription $formatDescription; + + /** + * @var ?string $currency ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR) + */ + #[JsonProperty('currency')] + private ?string $currency; + + /** + * @var ?string $aliasMember When measure is defined in View, it keeps the original path: Cube.measure + */ + #[JsonProperty('aliasMember')] + private ?string $aliasMember; + + /** + * @param array{ + * name: string, + * type: string, + * title?: ?string, + * shortTitle?: ?string, + * description?: ?string, + * aggType?: ?string, + * meta?: ?array, + * format?: ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null, + * formatDescription?: ?FormatDescription, + * currency?: ?string, + * aliasMember?: ?string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->title = $values['title'] ?? null; + $this->shortTitle = $values['shortTitle'] ?? null; + $this->description = $values['description'] ?? null; + $this->type = $values['type']; + $this->aggType = $values['aggType'] ?? null; + $this->meta = $values['meta'] ?? null; + $this->format = $values['format'] ?? null; + $this->formatDescription = $values['formatDescription'] ?? null; + $this->currency = $values['currency'] ?? null; + $this->aliasMember = $values['aliasMember'] ?? null; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return ?string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param ?string $value + */ + public function setTitle(?string $value = null): self + { + $this->title = $value; + $this->_setField('title'); + return $this; + } + + /** + * @return ?string + */ + public function getShortTitle(): ?string + { + return $this->shortTitle; + } + + /** + * @param ?string $value + */ + public function setShortTitle(?string $value = null): self + { + $this->shortTitle = $value; + $this->_setField('shortTitle'); + return $this; + } + + /** + * @return ?string + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param ?string $value + */ + public function setDescription(?string $value = null): self + { + $this->description = $value; + $this->_setField('description'); + return $this; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param string $value + */ + public function setType(string $value): self + { + $this->type = $value; + $this->_setField('type'); + return $this; + } + + /** + * @return ?string + */ + public function getAggType(): ?string + { + return $this->aggType; + } + + /** + * @param ?string $value + */ + public function setAggType(?string $value = null): self + { + $this->aggType = $value; + $this->_setField('aggType'); + return $this; + } + + /** + * @return ?array + */ + public function getMeta(): ?array + { + return $this->meta; + } + + /** + * @param ?array $value + */ + public function setMeta(?array $value = null): self + { + $this->meta = $value; + $this->_setField('meta'); + return $this; + } + + /** + * @return ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null + */ + public function getFormat(): string|LinkFormat|CustomTimeFormat|CustomNumericFormat|null + { + return $this->format; + } + + /** + * @param ( + * value-of + * |LinkFormat + * |CustomTimeFormat + * |CustomNumericFormat + * )|null $value + */ + public function setFormat(string|LinkFormat|CustomTimeFormat|CustomNumericFormat|null $value = null): self + { + $this->format = $value; + $this->_setField('format'); + return $this; + } + + /** + * @return ?FormatDescription + */ + public function getFormatDescription(): ?FormatDescription + { + return $this->formatDescription; + } + + /** + * @param ?FormatDescription $value + */ + public function setFormatDescription(?FormatDescription $value = null): self + { + $this->formatDescription = $value; + $this->_setField('formatDescription'); + return $this; + } + + /** + * @return ?string + */ + public function getCurrency(): ?string + { + return $this->currency; + } + + /** + * @param ?string $value + */ + public function setCurrency(?string $value = null): self + { + $this->currency = $value; + $this->_setField('currency'); + return $this; + } + + /** + * @return ?string + */ + public function getAliasMember(): ?string + { + return $this->aliasMember; + } + + /** + * @param ?string $value + */ + public function setAliasMember(?string $value = null): self + { + $this->aliasMember = $value; + $this->_setField('aliasMember'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/MetadataResponse.php b/src/Types/MetadataResponse.php new file mode 100644 index 00000000..bd7149cd --- /dev/null +++ b/src/Types/MetadataResponse.php @@ -0,0 +1,79 @@ + $cubes + */ + #[JsonProperty('cubes'), ArrayType([Cube::class])] + private ?array $cubes; + + /** + * @var ?string $compilerId + */ + #[JsonProperty('compilerId')] + private ?string $compilerId; + + /** + * @param array{ + * cubes?: ?array, + * compilerId?: ?string, + * } $values + */ + public function __construct( + array $values = [], + ) { + $this->cubes = $values['cubes'] ?? null; + $this->compilerId = $values['compilerId'] ?? null; + } + + /** + * @return ?array + */ + public function getCubes(): ?array + { + return $this->cubes; + } + + /** + * @param ?array $value + */ + public function setCubes(?array $value = null): self + { + $this->cubes = $value; + $this->_setField('cubes'); + return $this; + } + + /** + * @return ?string + */ + public function getCompilerId(): ?string + { + return $this->compilerId; + } + + /** + * @param ?string $value + */ + public function setCompilerId(?string $value = null): self + { + $this->compilerId = $value; + $this->_setField('compilerId'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/NestedFolder.php b/src/Types/NestedFolder.php new file mode 100644 index 00000000..12f9aafb --- /dev/null +++ b/src/Types/NestedFolder.php @@ -0,0 +1,79 @@ + $members + */ + #[JsonProperty('members'), ArrayType(['string'])] + private array $members; + + /** + * @param array{ + * name: string, + * members: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->members = $values['members']; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return array + */ + public function getMembers(): array + { + return $this->members; + } + + /** + * @param array $value + */ + public function setMembers(array $value): self + { + $this->members = $value; + $this->_setField('members'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/Query.php b/src/Types/Query.php new file mode 100644 index 00000000..c99e37f7 --- /dev/null +++ b/src/Types/Query.php @@ -0,0 +1,382 @@ + $measures + */ + #[JsonProperty('measures'), ArrayType(['string'])] + private ?array $measures; + + /** + * @var ?array $dimensions + */ + #[JsonProperty('dimensions'), ArrayType(['string'])] + private ?array $dimensions; + + /** + * @var ?array $segments + */ + #[JsonProperty('segments'), ArrayType(['string'])] + private ?array $segments; + + /** + * @var ?array $timeDimensions + */ + #[JsonProperty('timeDimensions'), ArrayType([TimeDimension::class])] + private ?array $timeDimensions; + + /** + * @var ?array> $order + */ + #[JsonProperty('order'), ArrayType([['string']])] + private ?array $order; + + /** + * @var ?int $limit + */ + #[JsonProperty('limit')] + private ?int $limit; + + /** + * @var ?int $offset + */ + #[JsonProperty('offset')] + private ?int $offset; + + /** + * @var ?array<( + * QueryFilterCondition + * |QueryFilterOr + * |QueryFilterAnd + * )> $filters + */ + #[JsonProperty('filters'), ArrayType([new Union(QueryFilterCondition::class, QueryFilterOr::class, QueryFilterAnd::class)])] + private ?array $filters; + + /** + * @var ?bool $ungrouped + */ + #[JsonProperty('ungrouped')] + private ?bool $ungrouped; + + /** + * @var ?array $subqueryJoins + */ + #[JsonProperty('subqueryJoins'), ArrayType([JoinSubquery::class])] + private ?array $subqueryJoins; + + /** + * @var ?array> $joinHints + */ + #[JsonProperty('joinHints'), ArrayType([['string']])] + private ?array $joinHints; + + /** + * @var ?string $timezone + */ + #[JsonProperty('timezone')] + private ?string $timezone; + + /** + * @var ?value-of $responseFormat + */ + #[JsonProperty('responseFormat')] + private ?string $responseFormat; + + /** + * @param array{ + * measures?: ?array, + * dimensions?: ?array, + * segments?: ?array, + * timeDimensions?: ?array, + * order?: ?array>, + * limit?: ?int, + * offset?: ?int, + * filters?: ?array<( + * QueryFilterCondition + * |QueryFilterOr + * |QueryFilterAnd + * )>, + * ungrouped?: ?bool, + * subqueryJoins?: ?array, + * joinHints?: ?array>, + * timezone?: ?string, + * responseFormat?: ?value-of, + * } $values + */ + public function __construct( + array $values = [], + ) { + $this->measures = $values['measures'] ?? null; + $this->dimensions = $values['dimensions'] ?? null; + $this->segments = $values['segments'] ?? null; + $this->timeDimensions = $values['timeDimensions'] ?? null; + $this->order = $values['order'] ?? null; + $this->limit = $values['limit'] ?? null; + $this->offset = $values['offset'] ?? null; + $this->filters = $values['filters'] ?? null; + $this->ungrouped = $values['ungrouped'] ?? null; + $this->subqueryJoins = $values['subqueryJoins'] ?? null; + $this->joinHints = $values['joinHints'] ?? null; + $this->timezone = $values['timezone'] ?? null; + $this->responseFormat = $values['responseFormat'] ?? null; + } + + /** + * @return ?array + */ + public function getMeasures(): ?array + { + return $this->measures; + } + + /** + * @param ?array $value + */ + public function setMeasures(?array $value = null): self + { + $this->measures = $value; + $this->_setField('measures'); + return $this; + } + + /** + * @return ?array + */ + public function getDimensions(): ?array + { + return $this->dimensions; + } + + /** + * @param ?array $value + */ + public function setDimensions(?array $value = null): self + { + $this->dimensions = $value; + $this->_setField('dimensions'); + return $this; + } + + /** + * @return ?array + */ + public function getSegments(): ?array + { + return $this->segments; + } + + /** + * @param ?array $value + */ + public function setSegments(?array $value = null): self + { + $this->segments = $value; + $this->_setField('segments'); + return $this; + } + + /** + * @return ?array + */ + public function getTimeDimensions(): ?array + { + return $this->timeDimensions; + } + + /** + * @param ?array $value + */ + public function setTimeDimensions(?array $value = null): self + { + $this->timeDimensions = $value; + $this->_setField('timeDimensions'); + return $this; + } + + /** + * @return ?array> + */ + public function getOrder(): ?array + { + return $this->order; + } + + /** + * @param ?array> $value + */ + public function setOrder(?array $value = null): self + { + $this->order = $value; + $this->_setField('order'); + return $this; + } + + /** + * @return ?int + */ + public function getLimit(): ?int + { + return $this->limit; + } + + /** + * @param ?int $value + */ + public function setLimit(?int $value = null): self + { + $this->limit = $value; + $this->_setField('limit'); + return $this; + } + + /** + * @return ?int + */ + public function getOffset(): ?int + { + return $this->offset; + } + + /** + * @param ?int $value + */ + public function setOffset(?int $value = null): self + { + $this->offset = $value; + $this->_setField('offset'); + return $this; + } + + /** + * @return ?array<( + * QueryFilterCondition + * |QueryFilterOr + * |QueryFilterAnd + * )> + */ + public function getFilters(): ?array + { + return $this->filters; + } + + /** + * @param ?array<( + * QueryFilterCondition + * |QueryFilterOr + * |QueryFilterAnd + * )> $value + */ + public function setFilters(?array $value = null): self + { + $this->filters = $value; + $this->_setField('filters'); + return $this; + } + + /** + * @return ?bool + */ + public function getUngrouped(): ?bool + { + return $this->ungrouped; + } + + /** + * @param ?bool $value + */ + public function setUngrouped(?bool $value = null): self + { + $this->ungrouped = $value; + $this->_setField('ungrouped'); + return $this; + } + + /** + * @return ?array + */ + public function getSubqueryJoins(): ?array + { + return $this->subqueryJoins; + } + + /** + * @param ?array $value + */ + public function setSubqueryJoins(?array $value = null): self + { + $this->subqueryJoins = $value; + $this->_setField('subqueryJoins'); + return $this; + } + + /** + * @return ?array> + */ + public function getJoinHints(): ?array + { + return $this->joinHints; + } + + /** + * @param ?array> $value + */ + public function setJoinHints(?array $value = null): self + { + $this->joinHints = $value; + $this->_setField('joinHints'); + return $this; + } + + /** + * @return ?string + */ + public function getTimezone(): ?string + { + return $this->timezone; + } + + /** + * @param ?string $value + */ + public function setTimezone(?string $value = null): self + { + $this->timezone = $value; + $this->_setField('timezone'); + return $this; + } + + /** + * @return ?value-of + */ + public function getResponseFormat(): ?string + { + return $this->responseFormat; + } + + /** + * @param ?value-of $value + */ + public function setResponseFormat(?string $value = null): self + { + $this->responseFormat = $value; + $this->_setField('responseFormat'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/QueryFilterAnd.php b/src/Types/QueryFilterAnd.php new file mode 100644 index 00000000..e00d55ca --- /dev/null +++ b/src/Types/QueryFilterAnd.php @@ -0,0 +1,53 @@ +> $and + */ + #[JsonProperty('and'), ArrayType([['string' => 'mixed']])] + private ?array $and; + + /** + * @param array{ + * and?: ?array>, + * } $values + */ + public function __construct( + array $values = [], + ) { + $this->and = $values['and'] ?? null; + } + + /** + * @return ?array> + */ + public function getAnd(): ?array + { + return $this->and; + } + + /** + * @param ?array> $value + */ + public function setAnd(?array $value = null): self + { + $this->and = $value; + $this->_setField('and'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/QueryFilterCondition.php b/src/Types/QueryFilterCondition.php new file mode 100644 index 00000000..156ee98c --- /dev/null +++ b/src/Types/QueryFilterCondition.php @@ -0,0 +1,105 @@ + $values + */ + #[JsonProperty('values'), ArrayType(['string'])] + private ?array $values; + + /** + * @param array{ + * member?: ?string, + * operator?: ?string, + * values?: ?array, + * } $values + */ + public function __construct( + array $values = [], + ) { + $this->member = $values['member'] ?? null; + $this->operator = $values['operator'] ?? null; + $this->values = $values['values'] ?? null; + } + + /** + * @return ?string + */ + public function getMember(): ?string + { + return $this->member; + } + + /** + * @param ?string $value + */ + public function setMember(?string $value = null): self + { + $this->member = $value; + $this->_setField('member'); + return $this; + } + + /** + * @return ?string + */ + public function getOperator(): ?string + { + return $this->operator; + } + + /** + * @param ?string $value + */ + public function setOperator(?string $value = null): self + { + $this->operator = $value; + $this->_setField('operator'); + return $this; + } + + /** + * @return ?array + */ + public function getValues(): ?array + { + return $this->values; + } + + /** + * @param ?array $value + */ + public function setValues(?array $value = null): self + { + $this->values = $value; + $this->_setField('values'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/QueryFilterOr.php b/src/Types/QueryFilterOr.php new file mode 100644 index 00000000..0b4dd018 --- /dev/null +++ b/src/Types/QueryFilterOr.php @@ -0,0 +1,53 @@ +> $or + */ + #[JsonProperty('or'), ArrayType([['string' => 'mixed']])] + private ?array $or; + + /** + * @param array{ + * or?: ?array>, + * } $values + */ + public function __construct( + array $values = [], + ) { + $this->or = $values['or'] ?? null; + } + + /** + * @return ?array> + */ + public function getOr(): ?array + { + return $this->or; + } + + /** + * @param ?array> $value + */ + public function setOr(?array $value = null): self + { + $this->or = $value; + $this->_setField('or'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/ReportingError.php b/src/Types/ReportingError.php new file mode 100644 index 00000000..711e71b4 --- /dev/null +++ b/src/Types/ReportingError.php @@ -0,0 +1,55 @@ +error = $values['error']; + } + + /** + * @return string + */ + public function getError(): string + { + return $this->error; + } + + /** + * @param string $value + */ + public function setError(string $value): self + { + $this->error = $value; + $this->_setField('error'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/ResponseFormat.php b/src/Types/ResponseFormat.php new file mode 100644 index 00000000..d3ea513b --- /dev/null +++ b/src/Types/ResponseFormat.php @@ -0,0 +1,10 @@ + $meta + */ + #[JsonProperty('meta'), ArrayType(['string' => 'mixed'])] + private ?array $meta; + + /** + * @param array{ + * name: string, + * title: string, + * shortTitle: string, + * description?: ?string, + * meta?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->title = $values['title']; + $this->description = $values['description'] ?? null; + $this->shortTitle = $values['shortTitle']; + $this->meta = $values['meta'] ?? null; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $value + */ + public function setName(string $value): self + { + $this->name = $value; + $this->_setField('name'); + return $this; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $value + */ + public function setTitle(string $value): self + { + $this->title = $value; + $this->_setField('title'); + return $this; + } + + /** + * @return ?string + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param ?string $value + */ + public function setDescription(?string $value = null): self + { + $this->description = $value; + $this->_setField('description'); + return $this; + } + + /** + * @return string + */ + public function getShortTitle(): string + { + return $this->shortTitle; + } + + /** + * @param string $value + */ + public function setShortTitle(string $value): self + { + $this->shortTitle = $value; + $this->_setField('shortTitle'); + return $this; + } + + /** + * @return ?array + */ + public function getMeta(): ?array + { + return $this->meta; + } + + /** + * @param ?array $value + */ + public function setMeta(?array $value = null): self + { + $this->meta = $value; + $this->_setField('meta'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Types/SimpleFormat.php b/src/Types/SimpleFormat.php new file mode 100644 index 00000000..3d534723 --- /dev/null +++ b/src/Types/SimpleFormat.php @@ -0,0 +1,13 @@ + $dateRange + */ + #[JsonProperty('dateRange'), ArrayType(['string' => 'mixed'])] + private ?array $dateRange; + + /** + * @param array{ + * dimension: string, + * granularity?: ?string, + * dateRange?: ?array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->dimension = $values['dimension']; + $this->granularity = $values['granularity'] ?? null; + $this->dateRange = $values['dateRange'] ?? null; + } + + /** + * @return string + */ + public function getDimension(): string + { + return $this->dimension; + } + + /** + * @param string $value + */ + public function setDimension(string $value): self + { + $this->dimension = $value; + $this->_setField('dimension'); + return $this; + } + + /** + * @return ?string + */ + public function getGranularity(): ?string + { + return $this->granularity; + } + + /** + * @param ?string $value + */ + public function setGranularity(?string $value = null): self + { + $this->granularity = $value; + $this->_setField('granularity'); + return $this; + } + + /** + * @return ?array + */ + public function getDateRange(): ?array + { + return $this->dateRange; + } + + /** + * @param ?array $value + */ + public function setDateRange(?array $value = null): self + { + $this->dateRange = $value; + $this->_setField('dateRange'); + return $this; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->toJson(); + } +} From 225dab0f345a4d16fd89a3244d8a8a8bd88017ed Mon Sep 17 00:00:00 2001 From: fern-support <126544928+fern-support@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:38:55 -0400 Subject: [PATCH 2/3] Add Reporting API polling helper + tests (FER-11257) The Reporting API's /reporting/v1/load endpoint is asynchronous: a still-processing query returns HTTP 200 with {"error":"Continue wait"} and must be re-sent until results arrive. - ReportingHelper::loadAndWait wraps reporting->load in an exponential- backoff retry loop (defaults: 2s -> 20s, factor 2, 20 attempts) with an optional shouldCancel predicate. Under PHP strict typing the sentinel body omits the non-nullable LoadResponse::$results, so the generated deserializer raises a TypeError; that is the retry signal (real API / transport errors propagate). Exported via src/Utils, .fernignore-protected. - Offline unit tests (tests/Integration/ReportingHelperTest.php) exercise the loop through the real LoadResponse deserializer, including a probe proving the Continue-wait body raises TypeError. - Live smoke test (tests/Integration/ReportingTest.php) targets production and is skipped unless TEST_SQUARE_REPORTING is set. - README: hand-authored Reporting API section documenting getMetadata, the Continue-wait behavior, and loadAndWait options. --- .fernignore | 1 + README.md | 71 ++++++++++ src/Utils/ReportingHelper.php | 149 ++++++++++++++++++++ tests/Integration/ReportingHelperTest.php | 163 ++++++++++++++++++++++ tests/Integration/ReportingTest.php | 67 +++++++++ 5 files changed, 451 insertions(+) create mode 100644 src/Utils/ReportingHelper.php create mode 100644 tests/Integration/ReportingHelperTest.php create mode 100644 tests/Integration/ReportingTest.php diff --git a/.fernignore b/.fernignore index 74dd6751..7a790668 100644 --- a/.fernignore +++ b/.fernignore @@ -6,6 +6,7 @@ phpstan.neon src/Exceptions/SquareApiException.php src/Legacy src/Utils/WebhooksHelper.php +src/Utils/ReportingHelper.php tests/Integration .fern/replay.lock .fern/replay.yml diff --git a/README.md b/README.md index 4fdb3852..31b35304 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,77 @@ $isValid = WebhooksHelper.verifySignature( ) ``` +## Reporting API + +The [Square Reporting API](https://developer.squareup.com/docs/reporting-api/overview) lets you +query aggregated business data. Start by calling `getMetadata` to discover the schema — the cubes, +views, measures, dimensions, and segments you can reference — then run a query with `load`: + +```php +$metadata = $client->reporting->getMetadata(); + +$response = $client->reporting->load(new Square\Reporting\Requests\LoadRequest([ + 'query' => new Square\Types\Query([ + 'measures' => ['Orders.count'], + ]), +])); +``` + +### Polling for long-running queries + +The `load` endpoint is asynchronous. While a query is still being computed, the API responds with +an HTTP `200` whose body is `{ "error": "Continue wait" }` instead of results, and the client is +expected to re-send the identical request until the results are ready. The SDK provides +`ReportingHelper::loadAndWait`, which owns that retry loop with exponential backoff: + +```php +use Square\Utils\ReportingHelper; +use Square\Reporting\Requests\LoadRequest; +use Square\Types\Query; + +$response = ReportingHelper::loadAndWait( + $client, + new LoadRequest([ + 'query' => new Query([ + 'measures' => ['Orders.count'], + ]), + ]), +); + +foreach ($response->getResults() as $result) { + // ... +} +``` + +`loadAndWait` accepts an options array to tune the polling behavior: + +| Option | Default | Description | +| --- | --- | --- | +| `maxAttempts` | `20` | Maximum poll attempts before giving up. | +| `initialDelayMs` | `2000` | Delay before the first retry, in milliseconds. | +| `maxDelayMs` | `20000` | Upper bound on the backoff delay, in milliseconds. | +| `backoffFactor` | `2` | Multiplier applied to the delay after each attempt. | +| `shouldCancel` | `null` | A `callable(): bool` polled before each attempt (and during the wait); aborts the loop when it returns `true`. | +| `requestOptions` | `null` | Per-request options forwarded to each underlying `reporting->load` call. | + +```php +$response = ReportingHelper::loadAndWait( + $client, + new LoadRequest([/* ... */]), + [ + 'maxAttempts' => 30, + 'initialDelayMs' => 1000, + 'shouldCancel' => fn (): bool => /* e.g. a deadline check */ false, + ], +); +``` + +If the query does not resolve within `maxAttempts`, or if `shouldCancel` aborts it, a +`Square\Exceptions\SquareException` is thrown. + +> **Note:** The Reporting API is available in **production only** and requires a +> reporting-provisioned access token; it is not available in the sandbox environment. + ## Legacy SDK While the new SDK has a lot of improvements, we at Square understand that it takes time to upgrade when there are breaking changes. diff --git a/src/Utils/ReportingHelper.php b/src/Utils/ReportingHelper.php new file mode 100644 index 00000000..7b19b7a9 --- /dev/null +++ b/src/Utils/ReportingHelper.php @@ -0,0 +1,149 @@ +load()` raises a `TypeError` while deserializing it rather than + * returning a populated object. That `TypeError` — distinct from the + * `SquareApiException` / `SquareException` raised for real API and transport + * failures, which are allowed to propagate — is the retry signal. (Should the + * `LoadResponse` schema ever make `results` optional, the sentinel would instead + * survive as an unmapped `error` property; the helper checks for that case too.) + */ +class ReportingHelper +{ + /** + * Sentinel value returned by the Reporting API on an HTTP 200 while a + * `/reporting/v1/load` query is still processing. It is NOT an error — the + * request should be retried. + */ + private const CONTINUE_WAIT = 'Continue wait'; + + /** + * Runs a reporting query and transparently polls until it resolves, returning + * the final {@see LoadResponse}. Re-sends the identical request with exponential + * backoff while the API answers "Continue wait". + * + * @param SquareClient $client The configured Square client. + * @param LoadRequest $request The reporting query (same shape as `reporting->load`). + * @param ?array{ + * maxAttempts?: int, + * initialDelayMs?: int, + * maxDelayMs?: int, + * backoffFactor?: float, + * shouldCancel?: callable(): bool, + * requestOptions?: array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * }, + * } $options Polling/backoff configuration: + * - `maxAttempts` Maximum poll attempts before giving up. Default 20. + * - `initialDelayMs` Delay before the first retry, in ms. Default 2000. + * - `maxDelayMs` Upper bound on the backoff delay, in ms. Default 20000. + * - `backoffFactor` Multiplier applied to the delay after each attempt. Default 2. + * - `shouldCancel` Predicate polled before each attempt and during the + * backoff wait; aborts the loop when it returns `true`. + * - `requestOptions` Forwarded to each underlying `reporting->load` call. + * @return LoadResponse The resolved response (never the "Continue wait" sentinel). + * @throws SquareException If the query does not resolve within `maxAttempts`, or if cancelled. + */ + public static function loadAndWait( + SquareClient $client, + LoadRequest $request = new LoadRequest(), + ?array $options = null, + ): LoadResponse { + $options ??= []; + $maxAttempts = $options['maxAttempts'] ?? 20; + $initialDelayMs = $options['initialDelayMs'] ?? 2000; + $maxDelayMs = $options['maxDelayMs'] ?? 20000; + $backoffFactor = $options['backoffFactor'] ?? 2; + $shouldCancel = $options['shouldCancel'] ?? null; + $requestOptions = $options['requestOptions'] ?? null; + + $delayMs = $initialDelayMs; + for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) { + if ($shouldCancel !== null && $shouldCancel()) { + throw new SquareException(message: 'Reporting query polling was cancelled.'); + } + + try { + $response = $client->reporting->load($request, $requestOptions); + if (!self::isContinueWait($response)) { + return $response; + } + } catch (TypeError) { + // The still-processing "Continue wait" body omits the required + // `results` field, so the generated deserializer raises a TypeError. + // Real API/transport failures raise SquareApiException/SquareException + // and are intentionally left to propagate. + } + + if ($attempt === $maxAttempts) { + break; + } + self::sleep($delayMs, $shouldCancel); + $delayMs = (int) min($delayMs * $backoffFactor, $maxDelayMs); + } + + throw new SquareException( + message: sprintf( + 'Reporting query did not complete after %d attempts ("%s").', + $maxAttempts, + self::CONTINUE_WAIT, + ), + ); + } + + /** + * Forward-compatible sentinel check: if the generated `LoadResponse` ever makes + * `results` optional, a "Continue wait" body would deserialize successfully with + * the unmapped `error` field preserved as an additional property. Treat that as + * a retry signal rather than a result. + */ + private static function isContinueWait(LoadResponse $response): bool + { + return ($response->getAdditionalProperties()['error'] ?? null) === self::CONTINUE_WAIT; + } + + /** + * Sleeps for the given number of milliseconds, polling `$shouldCancel` in small + * slices so cancellation stays responsive during a long backoff wait. + * + * @param ?callable(): bool $shouldCancel + * @throws SquareException If cancelled mid-wait. + */ + private static function sleep(int $ms, ?callable $shouldCancel): void + { + $sliceMs = 100; + $remaining = $ms; + while ($remaining > 0) { + if ($shouldCancel !== null && $shouldCancel()) { + throw new SquareException(message: 'Reporting query polling was cancelled.'); + } + $chunk = min($sliceMs, $remaining); + usleep($chunk * 1000); + $remaining -= $chunk; + } + } +} diff --git a/tests/Integration/ReportingHelperTest.php b/tests/Integration/ReportingHelperTest.php new file mode 100644 index 00000000..82b933fd --- /dev/null +++ b/tests/Integration/ReportingHelperTest.php @@ -0,0 +1,163 @@ + [ + new LoadResult([ + 'annotation' => new LoadResultAnnotation([ + 'measures' => [], + 'dimensions' => [], + 'segments' => [], + 'timeDimensions' => [], + ]), + 'data' => [['Orders.count' => '128']], + ]), + ], + ]); + } + + /** + * Builds a SquareClient whose `reporting->load` returns the next scripted entry + * (the last entry repeats once exhausted), recording the call count. A string + * entry is deserialized via the real `LoadResponse::fromJson`; a `LoadResponse` + * entry is returned as-is. + * + * @param array $sequence One entry per expected `load` call. + * @param int &$callCount Receives the number of `load` invocations. + */ + private function clientReturning(array $sequence, int &$callCount): SquareClient + { + $callCount = 0; + $reporting = $this->getMockBuilder(ReportingClient::class) + ->disableOriginalConstructor() + ->onlyMethods(['load']) + ->getMock(); + $reporting->method('load')->willReturnCallback( + function () use ($sequence, &$callCount): LoadResponse { + $entry = $sequence[min($callCount, count($sequence) - 1)]; + $callCount++; + // Real deserialization for the sentinel: a "Continue wait" body raises + // a TypeError here, exactly as the generated client does in production. + return is_string($entry) ? LoadResponse::fromJson($entry) : $entry; + } + ); + + $client = new SquareClient('test-token'); + $client->reporting = $reporting; + return $client; + } + + public function testPollsPastContinueWaitAndReturnsResolvedResult(): void + { + $callCount = 0; + $client = $this->clientReturning( + [self::CONTINUE_WAIT_BODY, self::CONTINUE_WAIT_BODY, self::resolvedResponse()], + $callCount, + ); + + $response = ReportingHelper::loadAndWait( + $client, + new LoadRequest(), + ['initialDelayMs' => 1, 'maxDelayMs' => 1, 'maxAttempts' => 5], + ); + + $this->assertCount(1, $response->getResults()); + $this->assertArrayNotHasKey('error', $response->getAdditionalProperties()); + $this->assertSame(3, $callCount); + } + + public function testReturnsImmediatelyWhenFirstResponseHasResults(): void + { + $callCount = 0; + $client = $this->clientReturning([self::resolvedResponse()], $callCount); + + $response = ReportingHelper::loadAndWait($client, new LoadRequest(), ['initialDelayMs' => 1]); + + $this->assertCount(1, $response->getResults()); + $this->assertSame(1, $callCount); + } + + public function testThrowsOnceMaxAttemptsExhausted(): void + { + $callCount = 0; + $client = $this->clientReturning([self::CONTINUE_WAIT_BODY], $callCount); // never resolves + + try { + ReportingHelper::loadAndWait( + $client, + new LoadRequest(), + ['initialDelayMs' => 1, 'maxDelayMs' => 1, 'maxAttempts' => 3], + ); + $this->fail('Expected SquareException was not thrown.'); + } catch (SquareException $e) { + $this->assertStringContainsString('did not complete after 3 attempts', $e->getMessage()); + } + $this->assertSame(3, $callCount); + } + + public function testCancellationAbortsPolling(): void + { + $callCount = 0; + $client = $this->clientReturning([self::CONTINUE_WAIT_BODY], $callCount); // would otherwise poll + + $this->expectException(SquareException::class); + $this->expectExceptionMessage('cancelled'); + + ReportingHelper::loadAndWait( + $client, + new LoadRequest(), + ['maxAttempts' => 10, 'shouldCancel' => fn (): bool => true], + ); + } + + /** + * The crux of the design: the generated `reporting->load` deserializes the + * "Continue wait" body into a TypeError (the non-nullable `results` is absent). + * That raised TypeError is how `loadAndWait` recognizes the sentinel; if it ever + * stops throwing, the helper would mistake "Continue wait" for a result. + */ + public function testContinueWaitBodyRaisesTypeErrorOnDeserialization(): void + { + $this->expectException(TypeError::class); + LoadResponse::fromJson(self::CONTINUE_WAIT_BODY); + } + + /** A real (here, empty) result body deserializes cleanly, with no sentinel. */ + public function testResolvedBodyDeserializesCleanly(): void + { + $resolved = LoadResponse::fromJson('{"results":[]}'); + $this->assertSame([], $resolved->getResults()); + $this->assertArrayNotHasKey('error', $resolved->getAdditionalProperties()); + } +} diff --git a/tests/Integration/ReportingTest.php b/tests/Integration/ReportingTest.php new file mode 100644 index 00000000..d0a06bd5 --- /dev/null +++ b/tests/Integration/ReportingTest.php @@ -0,0 +1,67 @@ + Environments::Production->value], + ); + } + + public function testGetMetadataReturnsSchema(): void + { + $metadata = self::$client->reporting->getMetadata(); + $this->assertInstanceOf(MetadataResponse::class, $metadata); + } + + public function testLoadAndWaitResolvesQuery(): void + { + $response = ReportingHelper::loadAndWait( + self::$client, + new LoadRequest([ + 'query' => new Query([ + 'measures' => ['Orders.count'], + ]), + ]), + ); + + $this->assertInstanceOf(LoadResponse::class, $response); + $this->assertIsArray($response->getResults()); + } +} From 503eae63c17ba121b67713fe181af22315246237 Mon Sep 17 00:00:00 2001 From: fern-support Date: Mon, 15 Jun 2026 19:50:14 -0400 Subject: [PATCH 3/3] test(reporting): source live token from TEST_SQUARE_REPORTING (prod) --- .github/workflows/ci.yml | 1 + tests/Integration/ReportingTest.php | 16 ++++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 345e2f1d..bc588cbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: runs-on: ubuntu-latest env: TEST_SQUARE_TOKEN: ${{ secrets.TEST_SQUARE_TOKEN }} + TEST_SQUARE_REPORTING: ${{ secrets.TEST_SQUARE_REPORTING }} steps: - name: Checkout repo diff --git a/tests/Integration/ReportingTest.php b/tests/Integration/ReportingTest.php index d0a06bd5..1fddcfa9 100644 --- a/tests/Integration/ReportingTest.php +++ b/tests/Integration/ReportingTest.php @@ -3,7 +3,6 @@ namespace Square\Tests\Integration; use PHPUnit\Framework\TestCase; -use RuntimeException; use Square\Environments; use Square\Reporting\Requests\LoadRequest; use Square\SquareClient; @@ -18,9 +17,10 @@ * The Reporting API is **production only** — it is not available in sandbox * (sandbox host → 404, sandbox token against production → 401) — and requires a * reporting-provisioned token. To avoid breaking the default CI run, the whole - * suite is skipped unless `TEST_SQUARE_REPORTING` is set. When enabled it reuses - * the same `TEST_SQUARE_TOKEN` as the other integration tests, but points at the - * production environment. + * suite is skipped unless `TEST_SQUARE_REPORTING` is set. When enabled, + * `TEST_SQUARE_REPORTING` itself supplies the production reporting-provisioned + * access token used to authenticate against the production environment + * (`TEST_SQUARE_REPORTING=`). */ class ReportingTest extends TestCase { @@ -28,13 +28,9 @@ class ReportingTest extends TestCase public static function setUpBeforeClass(): void { - if (!getenv('TEST_SQUARE_REPORTING')) { - self::markTestSkipped('Set TEST_SQUARE_REPORTING (with a production reporting token in TEST_SQUARE_TOKEN) to run the Reporting API live tests.'); - } - - $token = getenv('TEST_SQUARE_TOKEN'); + $token = getenv('TEST_SQUARE_REPORTING'); if (!$token) { - throw new RuntimeException('TEST_SQUARE_TOKEN environment variable is not set.'); + self::markTestSkipped('Set TEST_SQUARE_REPORTING= to run the Reporting API live tests against production.'); } self::$client = new SquareClient(