Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ composer require "seamapi/seam:^4"

## Summary of breaking changes

| Change | Affects you if... |
| ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| [PHP 8.2+ required](#php-82-or-later-is-required) | You run PHP 8.0 or 8.1 |
| [`Seam\Seam` replaces `Seam\SeamClient`](#seamseam-replaces-seamseamclient) | You construct the client (everyone) |
| [`$seam->client` is the Guzzle client](#seam-client-is-the-guzzle-client) | You use `$seam->client`, `$seam->request()`, or the removed public properties |
| [Requests are retried and time out sooner](#requests-are-retried-and-time-out-sooner) | You depend on requests never being retried, or on the 60-second timeout |
| [`poll_until_ready` is replaced](#poll_until_ready-is-replaced-by-wait_for_action_attempt) | You call `$seam->action_attempts->poll_until_ready()` or rely on its 20 s/0.4 s timing |
| [Nested resource classes are namespaced](#nested-resource-classes-are-namespaced) | You type-hint nested classes such as `Seam\Resources\DeviceProperties` |
| [Missing required parameters fail locally](#missing-required-parameters-fail-locally) | You call endpoints with missing parameters and rely on the server's 400 response |
| [Preferred HTTP methods and URL search params](#endpoints-use-preferred-http-methods) | You inspect traffic in a proxy, mock server, or firewall rules |
| [Error handling refinements](#error-handling-refinements) | You compare `getRequestId()` to `""`, or depend on 3xx responses passing through |
| [Pagination metadata is a `Seam\Pagination`](#pagination-metadata-is-a-typed-object) | You treat the paginator's metadata as a `stdClass` |
| [`Seam\Version` replaces `Seam\Utils\PackageVersion`](#seamversion-replaces-packageversion) | You read the package version programmatically |
| Change | Affects you if... |
| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| [PHP 8.2+ required](#php-82-or-later-is-required) | You run PHP 8.0 or 8.1 |
| [`Seam\Seam` replaces `Seam\SeamClient`](#seamseam-replaces-seamseamclient) | You construct the client (everyone) |
| [`$seam->client` is the Guzzle client](#seam-client-is-the-guzzle-client) | You use `$seam->client`, `$seam->request()`, or the removed public properties |
| [Requests are retried and time out sooner](#requests-are-retried-and-time-out-sooner) | You depend on requests never being retried, or on the 60-second timeout |
| [`poll_until_ready` is replaced](#poll_until_ready-is-replaced-by-wait_for_action_attempt) | You call `$seam->action_attempts->poll_until_ready()` or rely on its 20 s/0.4 s timing |
| [Nested resource classes are namespaced](#nested-resource-classes-are-namespaced) | You type-hint nested classes such as `Seam\Resources\DeviceProperties` |
| [Resource constructors take required properties first](#resource-constructors-take-required-properties-first) | You construct a resource positionally rather than with named arguments |
| [Missing required parameters fail locally](#missing-required-parameters-fail-locally) | You call endpoints with missing parameters and rely on the server's 400 response |
| [Preferred HTTP methods and URL search params](#endpoints-use-preferred-http-methods) | You inspect traffic in a proxy, mock server, or firewall rules |
| [Error handling refinements](#error-handling-refinements) | You compare `getRequestId()` to `""`, or depend on 3xx responses passing through |
| [Pagination metadata is a `Seam\Pagination`](#pagination-metadata-is-a-typed-object) | You treat the paginator's metadata as a `stdClass` |
| [`Seam\Version` replaces `Seam\Utils\PackageVersion`](#seamversion-replaces-packageversion) | You read the package version programmatically |

## PHP 8.2 or later is required

Expand Down Expand Up @@ -131,6 +132,20 @@ This rename also fixes a class of bugs where two nested shapes competed for one

Property reads are unaffected — only explicit references to the nested class names need updating.

## Resource constructors take required properties first

In v3, resource constructor parameters were ordered by property name alone. In v4 required properties come first and optional ones follow, alphabetical within each group.

```php
// v3
new Seam\Resources\AcsUser($access_schedule, $acs_system_id, ...);

// v4
new Seam\Resources\AcsUser($acs_system_id, $acs_user_id, ..., access_schedule: $schedule);
```

This affects only positional construction. Reading properties and `from_json` are unaffected. Construct resources with [named arguments](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments).

## Missing required parameters fail locally

In v3, every endpoint parameter defaulted to `null`, so a call missing a required parameter was sent to the server and failed with `Seam\HttpInvalidInputError` after a round trip. In v4, required parameters have no default, so PHP itself rejects the call with an `ArgumentCountError` (or an `Error` for a missing named argument). Endpoints that require _at least one_ of their parameters throw `InvalidArgumentException` when called with none:
Expand Down
18 changes: 14 additions & 4 deletions codegen/lib/layouts/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
// into one braced namespace block per namespace. Each class contributes its
// from_json body lines and constructor parameter lines.
//
// The blueprint does not track which resource properties are required, so
// every property is optional: from_json falls back to null for missing values
// and the constructor parameters are nullable.
// Whether a property is required is carried on isOptional: an optional one
// gets a null default, a required one does not. Constructor parameters are
// emitted required first, since PHP deprecates an optional parameter
// declared before a required one.

import type {
ResourceClassProperty,
Expand Down Expand Up @@ -105,16 +106,25 @@ const getClassLayoutContext = (
a.name.localeCompare(b.name),
)

const parameterOrder = sortRequiredFirst(sorted)

return {
className: schema.name,
description: schema.description,
isDeprecated: schema.isDeprecated,
deprecationMessage: schema.deprecationMessage,
fromJsonProps: sorted.map(generateFromJsonProp),
constructorParams: sorted.map(generateConstructorParam),
constructorParams: parameterOrder.map(generateConstructorParam),
}
}

const sortRequiredFirst = (
properties: ResourceClassProperty[],
): ResourceClassProperty[] => [
...properties.filter(({ isOptional }) => !isOptional),
...properties.filter(({ isOptional }) => isOptional),
]

export const setResourceLayoutContext = (
resource: ResourceSchema,
): ResourceLayoutContext => {
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
cacheDirectory=".phpunit.cache"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
failOnDeprecation="true"
failOnRisky="true"
failOnWarning="true"
>
Expand Down
100 changes: 50 additions & 50 deletions src/Resources/AccessCode.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading