realestateconz/api-client-bundle
An API client bundle for realestate.co.nz that simplifies connecting PHP applications to property listing data. It provides configuration and request handling for querying real estate listings and related resources with less boilerplate.
Reality check first: this is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/*.php publish tag. A Laravel developer's fastest path is almost always to depend on the underlying realestateconz/api-client library directly and wire it by hand in a service provider, rather than trying to make Symfony's DI container and bundle bootstrapping cooperate with Laravel's container. Treat the bundle as reference documentation for how the client is meant to be configured.
If you are actually in a Symfony app:
composer require realestateconz/api-client-bundle
config/bundles.php:
RealestateConz\ApiClientBundle\RealestateConzApiClientBundle::class => ['all' => true],
DependencyInjection/Configuration.php and Extension.php (or the README's config block). That file is the authoritative list of what is configurable — base URI, API key/credentials, HTTP client, timeouts, serializer settings. Everything you need to know about the package's surface area is there.# config/packages/realestate_conz_api_client.yaml
realestate_conz_api_client:
api_key: '%env(REALESTATE_API_KEY)%'
First use case: a single read-only integration — pull a page of listings into a local table or a debug route — before touching writes, pagination, or caching. You want to validate key/auth/base-URI correctness in isolation.
Wrap it, don't scatter it (Laravel). Because you're bypassing the bundle, register the underlying client as a singleton in a dedicated service provider and inject an interface, not the concrete client:
// app/Providers/RealestateServiceProvider.php
$this->app->singleton(RealestateClient::class, fn ($app) => new RealestateClient(
apiKey: config('services.realestate.key'),
httpClient: $app->make(GuzzleHttp\Client::class),
));
Bind RealestateClient to your own thin PropertyRepository interface so application code never sees vendor types. This is the single most valuable pattern: it lets you swap the client, mock it, and insulate yourself from this package's low maintenance cadence.
Adapter/repository layer for API shape. Put request building, pagination params, and response → domain-model mapping in one class:
final class RealestateRepository implements PropertyRepository
{
public function search(SearchCriteria $c): PropertyPage
{
$response = $this->client->listings($c->toQuery());
return PropertyPage::fromApi($response); // your DTO, not theirs
}
}
Job-per-page for bulk sync. Realestate.co.nz listings are paginated. Model sync as queued jobs — one job per page — with RateLimited middleware, rather than a loop inside a single job. This gives you retries, backoff, and horizontal scaling for free:
RateLimited::for('realestate')->allow(10)->every(60)
Cache aggressively on reads. Listing data changes on the order of hours, not seconds. Wrap read calls in Cache::remember keyed by normalized query + page. It cuts quota usage dramatically and makes local development pleasant.
HTTP-level testing. Do not mock your own repository to test the integration; mock at the HTTP boundary with Http::fake() (or Guzzle's MockHandler) and assert on the outgoing request URI/params. Assert your adapter maps a fixture response correctly. Snapshot a couple of real API responses into tests/Fixtures/ early — the API's payload shape is the thing most likely to silently drift.
Config as a first-class boundary. Put base URL, key, timeout, and page size in config/services.php, sourced from .env. Never read env() outside config files (breaks under config:cache).
Symfony-side integration tips (if applicable): inject the registered client service rather than constructing it; use framework.http_client with a scoped client for timeouts/retries; add a kernel.exception listener or middleware for API error → domain exception translation.
composer.json to an exact version, commit composer.lock, and read the source before relying on it in production. Budget for vendoring or rewriting the thin client if it goes stale. The risk is not the code — it's that nobody else will report the bug you hit.env() returning null after config:cache.RequestException message will make debugging auth and parameter errors painful. Wrap in a domain exception early.How can I help you explore Laravel packages today?