{{ message }}
Repository navigation
Conversation
The client sent `source=php`, which made it impossible to tell PHP client versions apart in SerpApi usage statistics. Report `serpapi-php:<version>` instead, matching the `serpapi-ruby:<version>` convention. Query string assembly moves into a private `query()` method so it can be covered without issuing an HTTP request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The constructor only accepted api_key, engine and timeout, so parameters like location, hl, gl or no_cache had to be repeated on every search call. The Ruby client takes a hash where any unrecognized key becomes a default search parameter. Add a fourth positional argument for default parameters, and accept a configuration array as the first argument for parity with the Ruby client. Per-call parameters still take precedence, and null values are dropped from the query rather than sent empty. Expose the effective defaults via get_params(), alongside a get_timeout() accessor. The existing positional signature is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
var_dump() on a client printed the api_key property in clear text, so a key could end up in logs or a bug report. The Ruby client masks it in inspect. Add __debugInfo() to mask the key for var_dump and debuggers, plus an inspect() method for parity. Keys of 8 characters or fewer are replaced entirely rather than partially revealed. print_r() and var_export() read properties directly and cannot be hooked; this is called out in the docblock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every request created and destroyed its own cURL handle, paying for DNS, TCP and TLS setup each time. The Ruby client keeps a persistent socket and reports roughly twice the throughput because of it. Keep one cURL handle per client and reuse it, so curl holds the connection open between requests. Measured against the locations endpoint, follow-up requests drop from ~110ms to ~22ms. Persistent mode is on by default, matching the Ruby client, and can be turned off with `['persistent' => false]`. close() releases the connection and the client reconnects on the next request; the destructor closes it too, so existing code needs no change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Responses were always decoded to stdClass, with no way to get arrays. The Ruby client exposes symbolize_names for the equivalent choice. Add an `assoc` option, settable on the client or per call. Error and search_id extraction now reads either shape through a small dig() helper, so exceptions carry the same context in both modes. The default stays stdClass to keep existing code working, which is why this is opt-in rather than mirroring Ruby's default. search() and account() lose their `object` return type declaration since they can now return an array; the docblocks carry the union type instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The suite covered 20 engines against the Ruby client's 38. Add examples for amazon, yelp, yandex, google_news, google_news_light, google_images, google_images_light, google_light, google_light_search, google_finance, google_flights, google_hotels, google_patents, google_product, google_reverse_image, google_trends, google_videos, google_immersive_product and google_ai_overview. Two of these are written differently from their Ruby counterparts: - google_flights and google_hotels compute check-in and departure dates relative to today. The Ruby specs hardcode 2025 dates that have since passed. - google_ai_overview reads page_token from a live Google search instead of a hardcoded token, and skips when Google returns no overview. The Ruby spec pins an expired token. ClientIntegrationTest duplicated GoogleSearchTest assertion for assertion, so it is removed. These require an API key to run and were not executed here; only the key-free unit tests were. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Ruby client ships a demo directory used as out-of-box testing; the PHP
client had no runnable examples outside the test suite.
Add four scripts and a `make demo` target:
demo.php basic search
demo_suggest.php autocomplete, showing client-level default parameters
demo_async.php non-blocking batch submission collected via the
Search Archive API
demo_persistent.php connection reuse benchmark, needs no API key
demo_persistent.php measures a 2.6x speedup locally, in line with the Ruby
client's reported figure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI ran the test suite but nothing checked types, despite the codebase already carrying full @PARAM and @return annotations. The Ruby client gates its build on rubocop. Add PHPStan at level 6 over src/ and demo/, wired into `make analyse`, `composer run-script analyse` and the workflow. Analysis is skipped on the 7.2 and 7.3 matrix entries, which predate PHPStan 2. It caught one real problem: demo_async.php read ->search_metadata off a value that search() can also return as an array, which would have surfaced as a fatal error rather than a clear message. The demo now validates the response shape first. Test files are left out. Level 6 wants a return type on every test method, which is churn across 40 files for little benefit. The cURL handle type is excluded, with a comment: it is a resource on PHP 7 and a CurlHandle on PHP 8, and PHPStan only ever sees one version. No code style fixer is added; the codebase uses 2-space indentation, so PSR-12 enforcement would rewrite every file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test suite, demos and README used API_KEY, while the Ruby client and SerpApi's own documentation use SERPAPI_KEY. Anyone working across both libraries had to keep two names for the same secret. SERPAPI_KEY is now the documented name everywhere. The test helper still falls back to API_KEY so existing local setups keep working, and the workflow reads `secrets.SERPAPI_KEY || secrets.API_KEY` so CI does not break before the repository secret is renamed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New features since 1.0.0, all backwards compatible: connection reuse, default search parameters in the constructor, associative array decoding, API key masking in debug output and Markdown output. A minor bump under semver. Client::VERSION also feeds the `source` query parameter, so requests now report `serpapi-php:1.1.0` and the new release is distinguishable in usage statistics. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
strzibny
force-pushed
the
feature/ruby-parity
branch
from
October 21, 2026 12:20
92a4a26 to
e057e0d
Compare
This was referenced Oct 21, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Bumps
Client::VERSIONfrom1.0.0to1.1.0, plus the README change log.Why a minor bump
New features since 1.0.0, all backwards compatible:
persistent,close())assoc)__debugInfo(),inspect())markdown(),search_archive($id, 'md'))No removals and no signature breaks, so a minor bump under semver rather than a major one.
Side effect worth knowing
Client::VERSIONalso feeds thesourcequery parameter, so requests will start reportingserpapi-php:1.1.0instead ofserpapi-php:1.0.0. That is the intent — it makes the new release distinguishable in usage statistics — but it does mean this PR changes what goes over the wire, not just a constant.Verified:
Client::SOURCEresolves toserpapi-php:1.1.0, PHPStan level 6 clean, 108 tests passing.For the reviewer
Based on
feature/ruby-parity(#3), notInit, since it is those features this bump is versioning. Merge order: #3, then #5 (Markdown), then this.composer.jsonintentionally carries noversionfield — Packagist derives it from the git tag, and a hardcoded field would be a second source of truth to keep in sync. Tagging1.1.0after merge is what actually publishes the release.Related: the repo has no
CHANGELOG.md; the change log lives as a README section, which is what this PR updates. Splitting it into a real changelog file is worth doing separately.🤖 Generated with Claude Code