Skip to content

feat: sync PHP client with Apify OpenAPI spec v2-2026-10-19T133704Z - #17

Merged
Pijukatel merged 3 commits into
masterfrom
claude/dazzling-ritchie-u46frr
Oct 20, 2026
Merged

Pijukatel merged 3 commits into
masterfrom
claude/dazzling-ritchie-u46frr

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Syncs the client to OpenAPI spec v2-2026-10-19T133704Z (apify/apify-docs#3087) and ports the changes from the latest apify-client-js batch (apify/apify-client-js#1140, #1145, #1139, #1141, #1152).

  • apify-docs#3087 realigns the Actor, version, env var and schedule request schemas. No client code change was needed, because those request bodies are passed through untyped. New integration round-trip tests cover the two new fields (actorPermissionLevel and the schedule's notifications).
  • DatasetClient::streamItems() returns a stream over a dataset export instead of buffering it in memory (apify-client-js#1139, #1152). It shares a new ResourceContext::streamRaw() helper with LogClient::stream().
  • A $contentEncoding parameter on KeyValueStoreClient::setRecord() uploads a value that is already compressed, without compressing it again (apify-client-js#1140).
  • ApifyClientException is a new common base class for every error the client throws for a failed API call, and ApifyApiException now extends it (apify-client-js#1145).
  • InvalidResponseBodyException replaces the raw JsonException thrown when a successful response body isn't valid JSON. Like the reference client, the client now retries such a response with the same backoff it uses for a retryable status code. HttpClientCore::call() reads the body once and returns it in a fresh stream, so a non-seekable PSR-7 body from a custom HTTP client isn't read twice.

Realigns with the reference apify-client-js updates since the client's last sync
(apify/apify-client-js#1139, #1140, #1141, #1145, #1152) and the OpenAPI schema
fixes in apify/apify-docs#3087 (Actor/version/schedule/env-var request bodies,
which this client already passes through untyped, so no code changes were needed
for those specifically).

- Add DatasetClient::streamItems(), a streamed (non-buffered) counterpart to
  downloadItems() for large exports.
- Add a $contentEncoding parameter to KeyValueStoreClient::setRecord(), so an
  already-compressed value is uploaded as is instead of being compressed again.
- Add ApifyClientException, a common base for every error the client throws for
  a failed API interaction; ApifyApiException (and its status subclasses) now
  extend it.
- Add InvalidResponseBodyException (extends ApifyClientException), thrown
  instead of a raw JsonException when a successful response body fails to parse
  as JSON.
- Bump Version::API_SPEC_VERSION and Version::CLIENT_VERSION.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3FfQf9r45KVCa5VAYAjkd
HttpClientCore::call() now accepts an optional $validateBody hook, run against
the buffered response body inside the existing retry loop before a successful
status is returned; ResourceContext's JSON-decoding primitives (and the few
direct resource-client call sites that decode JSON themselves) pass their
decode step through it. A body that fails to parse as JSON - usually a
connection dropped mid-response - is now retried with the same backoff as a
5xx/429, instead of failing on the very first attempt, matching the reference
client's own retry-loop placement of this check.

Also: fix InvalidResponseBodyException's message to emit a real newline
(was a literal backslash-n from a single-quoted format string), update its
docblock to stop asserting the now-fixed non-retry behavior, and trim
changelog-trivia from DatasetClient::streamItems()'s docblock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3FfQf9r45KVCa5VAYAjkd
A validated (JSON-decoded) response body was read twice: call() ran $validateBody
against (string) $response->getBody() and discarded the result, then returned the
same $response; every JSON call site in ResourceContext then re-read the body via
a second (string) $response->getBody(). A seekable stream (the bundled Guzzle
client, MockTransport) rewinds transparently on each cast, masking this, but
PSR-18 does not guarantee a seekable/re-readable body, so a transport that
returns one exhausts it on the first read and the second read silently returns
'' - the resource then looks empty/absent with no exception. Buffer the body into
a string exactly once, validate it, and hand the caller a response rewrapped
around a fresh stream built from that buffered string via the injected
StreamFactoryInterface, which backs it with a seekable resource either way.

Added a regression test using a GuzzleHttp\Psr7\NoSeekStream response body
(confirmed it fails against the prior code) and a MockTransport::queueRawResponse()
helper to queue it.

Also collapsed the retry-on-malformed-JSON rationale, previously restated in three
docblocks (HttpClientCore::call's $validateBody @PARAM, Json::decode, and
InvalidResponseBodyException's class doc), down to one canonical copy on
HttpClientCore::call(), with the other two trimmed to a one-line cross-reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3FfQf9r45KVCa5VAYAjkd
@Pijukatel Pijukatel added adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team. labels Oct 19, 2026 — with Claude
@apify-service-account apify-service-account added the tested Temporary label used only programatically for some analytics. label Oct 19, 2026
@Pijukatel
Pijukatel merged commit b2340ac into master Oct 20, 2026
4 of 8 checks passed
@Pijukatel
Pijukatel deleted the claude/dazzling-ritchie-u46frr branch October 20, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants