Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phrets Laravel Package

neighborhoods/phrets

A PHP RETS client for connecting to MLS servers, running property searches, retrieving metadata, and downloading listing photos. Includes session handling and a straightforward API for integrating real estate data into PHP applications, with support for standard RETS transactions.

View on GitHub
Deep Wiki
Context7

Getting Started

PHRETS is a PHP client for the RETS (Real Estate Transaction Standard) protocol used by most North American MLS systems. If you're syncing listings, photos, or agent rosters into a Laravel app, this is the library you'll reach for.

Minimal setup:

  1. composer require troyhall/phrets (or neighborhoods/phrets — the 2.7.1 release comes from the neighborhoods/PHRETS fork, so pin the fork if you rely on the MultipleStreamed parser fix).
  2. Create a dedicated config file (config/rets.php) with login_url, username, password, user_agent, and optionally user_agent_password. Don't inline credentials in code — use .env.
  3. Wrap the client in a service provider / singleton so you're not logging in on every request.
  4. First use case: a single SearchQuery against the Property class ((ModificationTimestamp=2024-01-01T00:00:00Z+)) to confirm auth, metadata, and formatting work before you build a full sync.

Look first at: the RETS\Session class (login/search/getObject/disconnect), RETS\Metadata for discovering the actual field/class names your MLS exposes, and the SearchQuery/SearchResult helpers if you're doing anything non-trivial with result counting.

Implementation Patterns

Singleton client with explicit lifecycle. RETS sessions are stateful and server-side; treat RETS\Session as a request-scoped or job-scoped resource. Bind it as a singleton and register a disconnect() in a terminating callback, or better, use a small wrapper class you inject that handles login/logout in a try/finally.

Metadata caching is non-negotiable. GetMetadata is slow and expensive against live servers. Fetch it once per environment, serialize it (JSON or cache driver), and refresh on a schedule rather than per-run. The class list, field definitions, and lookup values rarely change.

Queue everything. Searches and GetObject calls are I/O-bound and slow. Dispatch a SyncListings job, use SearchQuery with pagination (offset/limit), and process results in chunks. RETS servers frequently cap returned rows (often 100–1000), so never assume a single search returns everything.

MultipleStreamed for large batches. If your MLS returns large Property result sets, the MultipleStreamed parser streams the multipart response instead of buffering it all in memory. This is exactly what the 2.7.1 Guzzle fix affects — if you're on 2.7.0 and see deprecation notices from GuzzleHttp\Psr7\parse_response, upgrading to 2.7.1 silences them without changing behavior.

Idempotent upserts. RETS data is dirty: duplicates, whitespace, 0 vs null, and inconsistent datetimes. Key your local listings table on the MLS ListingId and use updateOrCreate. Store the raw ModificationTimestamp so incremental syncs ((ModificationTimestamp={lastSync}+)) can resume cleanly.

Photos via GetObject. Photos are a separate call (GetObject($resource, $type, $id)) and often return multiple images per listing. Download to a temporary disk, then Storage::disk('s3')->put(...) — don't pipe directly to storage streams because the parser expects a writable local path in most configurations.

Rate limiting at the job layer. Use Laravel's RateLimiter or ->middleware(RateLimited::class) on sync jobs. MLS servers throttle aggressively and will ban IPs on abuse; a per-second cap tuned to your vendor's contract is safer than retry-on-429 (which many RETS servers don't return properly).

Gotchas and Tips

  • parse_response deprecation fixed in 2.7.1. If you're on 2.7.0 and use the MultipleStreamed parser with a newer Guzzle, you'll see deprecation noise. The upgrade is drop-in — no API change — but verify your Guzzle version matches the neighborhoods/phrets fork's constraints.
  • The fork matters. neighborhoods/PHRETS is an actively maintained fork; the upstream troyhall/PHRETS has been quieter. Confirm which one your composer.json resolves to, and pin the minor version if you need stability.
  • Session expiry mid-job. Long-running searches or photo fetches can outlive the session token. Catch exceptions from RETS\Exceptions\RetsException and re-login once; don't blindly retry loops.
  • User-Agent is mandatory. Most RETS servers reject requests without a User-Agent header, and some require the RETS-Version header too. Set both in config; the failure mode is an opaque "Login failed" with no diagnostics.
  • Datetimes are local-time with an offset. RETS uses YYYY-MM-DDTHH:MM:SS with an offset but no timezone name. Normalize to UTC with Carbon before comparison, or your incremental sync will silently skip or duplicate rows across DST changes.
  • SearchQuery is your friend. The lower-level RETS\Session::Search() returns a SearchResult; the query builder adds pagination, field selection, and query construction. Prefer it for anything beyond a one-off exploratory call.
  • Log the raw XML on failures. When a search fails against a quirky MLS, the exception message is often useless. Drop the raw response into a debug log (guarded by config) and inspect field encodings — encoding issues (Encoding="UTF-8" vs Latin-1) are a recurring culprit.
  • Extension points: RETS\Session accepts an injected PSR-7 client, so you can add Guzzle middleware for request logging, retries, or metrics without forking the package. That's the cleanest place to add observability.
  • Configuration quirks: the library reads login_url and username/password, but the user-agent password (if your vendor uses one) goes in a separate key. Double-check against your vendor's credentials sheet — a mislabeled user_agent_password is a common source of "unauthorized" errors.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource