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

Php Api Client Laravel Package

recombee/php-api-client

PHP client for the Recombee recommendation API, installable via Composer. Send recommendation, search, and event requests; batch large data; create users/items on the fly; and fetch follow-up recommendations with recommendation IDs. Supports regional endpoints and typed request/exception classes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure (5 minutes)

composer require recombee/php-api-client:^6.2

Add credentials to config/services.php — never instantiate the client with literals scattered through the codebase:

'recombee' => [
    'database_id' => env('RECOMBEE_DATABASE_ID'),
    'token'       => env('RECOMBEE_PRIVATE_TOKEN'),
    'region'      => env('RECOMBEE_REGION', 'eu-west'),   // pick the region closest to your app servers
    'timeout'     => env('RECOMBEE_TIMEOUT', 2),          // read path: fail fast
],

Bind it once in a service provider as a singleton, alongside a thin domain service that you actually inject:

$this->app->singleton(Client::class, fn () => new Client(
    config('services.recombee.database_id'),
    config('services.recombee.token'),
    ['region' => config('services.recombee.region'), 'timeout' => config('services.recombee.timeout')]
));

$this->app->singleton(RecommendationService::class);   // your wrapper, not the SDK

Where to look first

  1. src/Requests/ in the package — this directory is the API reference. Every Recombee operation is a class (AddPurchase, SetItemValues, RecommendItemsToUser, SearchItems, Batch, …). Skim the filenames before reading any external docs.
  2. The README examples for the two call shapes you'll use 95% of the time: a single Reqs\* request and a Reqs\Batch.
  3. The Recombee Admin UI (admin.recombee.com) — this is where scenarios, filters, and boosters live, and it's what makes the integration cheap to iterate on.
  4. src/Exceptions/ — ApiException is the parent; ResponseException (bad request / 4xx) and ApiTimeoutException (network / 5xx-ish) are what you catch.

First use case to ship: product/catalog sync → interaction tracking → a cached "Recommended for you" endpoint. Concretely:

  • An Artisan command (recombee:sync-catalog) that pages your products table and pushes SetItemValues in batches with cascadeCreate => true.
  • A model observer or domain event that queues AddDetailView / AddCartAddition / AddPurchase.
  • A GET /api/recommendations controller action returning cached RecommendItemsToUser results with a curated fallback.

That gets you a visible "Recommended for you" rail in a sprint, and the ingestion pipeline you'll need for everything else afterwards.

Implementation Patterns

1. Abstract behind an interface — the SDK types should not leak

The single highest-value decision. Define App\Contracts\Recommender with methods shaped like your domain (similarProducts(Product $p, ?User $u, int $limit): Collection), implement it with RecombeeRecommender, and bind it in the container. Benefits: you can swap to a fake in tests, swap vendors later, and controllers never import Recombee\RecommApi\Requests. The package is vendor-maintained with a small community (~26 stars, zero dependents) — a boundary costs you an hour now and buys you an exit ramp.

2. Write path: always asynchronous, always batched

Interaction events are the highest-volume calls and the least latency-sensitive. Never send() an AddPurchase inside a checkout request.

// App\Jobs\TrackInteraction
public function handle(Recommender $recommender): void
{
    $recommender->addPurchase($this->userId, $this->itemId, $this->recommId);
}

Dispatch with ->onQueue('recommendations')->afterCommit() (use afterCommit so you never push events for rolled-back transactions). For bulk imports (backfilling historical orders, nightly catalog sync) build an array of Reqs\* objects and send one Reqs\Batch. Keep batches to a few thousand requests — the API caps batch size, and large batches make partial failures coarse-grained and retries expensive. Use Bus::batch([...])->allowFailures()->onQueue('recommendations') so one bad row doesn't abort the sync, and record failures.

3. Use cascadeCreate on ingestion, but create properties first

cascadeCreate => true on AddPurchase, SetItemValues, SetUserValues means a new user or item is created on the fly rather than throwing. It does not create properties. Run AddItemProperty / AddUserProperty once, up front (in a migration-like Artisan command, or by hand), otherwise SetItemValues with an unknown property key fails.

4. Scenario-first recommendations (the biggest day-to-day win)

Rather than encoding filters and boosters in PHP, define scenarios in the Admin UI (product_detail, home_recommended, cart_cross_sell, search_top) and pass them from your code:

$client->send(new Reqs\RecommendItemsToItem($itemId, $userId, 12, ['scenario' => 'product_detail']));

Product and marketing can tune business rules without a deploy; you version them by adding new scenarios and switching the string. Reserve inline filter / booster / diversity options for genuinely dynamic, request-specific logic (e.g., "only in-stock, only this price band").

5. Pagination and attribution via recommId

RecommendItemsToUser / RecommendItemsToItem return a recommId plus the items. Two uses:

  • Infinite scroll: new Reqs\RecommendNextItems($recommId, 10) returns the next slice of the same ranked list — no re-ranking jitter between pages. Stash the recommId in the session or cache keyed by the page token.
  • Attribution: pass recommId into the follow-up AddPurchase / AddCartAddition request. This closes the loop so Recombee (and your A/B analysis) can attribute conversions to the scenario that produced them. If you're evaluating "did personalization lift revenue?", this is the mechanism — instrument it from day one, not later.

6. Cache the read path, but keep it short

Recommendations are per-user and change as behaviour changes. Cache 5–15 minutes in Redis under recs:{user}:{scenario}:{limit} with a remember(); invalidate on major profile events if you care. This collapses API latency out of the page render and cuts cost. For the recommId flow, caching is fine as long as you return the cached recommId too.

7. Treat failures as a first-class state

Every call site should have a fallback. Two tiers:

  • Read path: try/catch ApiTimeoutException then ResponseException then ApiException; on failure serve a cached last-good response, else a cheap Eloquent fallback (where('is_bestseller') / top-sellers by 30-day sales). Never let an empty rail reach the UI; a "Popular" heading beats a hole in the layout.
  • Write path: let the job retry with backoff ($tries = 3, backoff = [10, 60]), then dead-letter to a recombee_failed_interactions table or a failed-jobs dashboard. Silent loss of interaction data degrades recommendation quality over weeks in ways nobody notices.

8. Keep a separate client for the read path

Read-path calls sit in the request cycle, so they should have a short timeout (1–2s). Ingestion jobs can afford 10s+. Bind Client for writes and Client::class . '.fast' (or construct inline in the read service) with a lower timeout — this one config difference prevents a slow Recombee response from eating your p95.

9. Model observers for catalog sync

Product::saved(fn (Product $p) => SyncProductToRecommender::dispatch($p->id));
Product::deleted(fn (Product $p) => RemoveProductFromRecommender::dispatch($p->id));

Send the fields the recommender actually reasons over: name/description, category, brand, price, in_stock_from, image URL — plus booleans for filters (is_active). Register AddItemProperty types deliberately (double, int, string, timestamp, image, boolean, set, tokenized); a price stored as string silently breaks numeric filters and boosters.

10. Testing

Do not hit Recombee in the test suite. Resolve Recommender to a fake in TestCase::setUp() that returns deterministic IDs; assert that controllers render the rail and that jobs dispatch. Add one or two integration tests behind an env flag (RECOMBEE_INTEGRATION=true) that hit a dedicated scratch database — mainly for the ingestion command and property schema. Use ResetDatabase in those tests, guarded.

Gotchas and Tips

IDs must be strings — and PHP will happily hand you integers. Recombee entities are keyed by string IDs; 42 and "42" are not guaranteed to be the same entity, and integer PKs passed straight through create silent duplicates that split interaction signal. Cast explicitly: (string) $product->id, and be consistent about prefixes (item-42, user-42) — prefixes make it impossible to confuse a user ID with an item ID in a filter or a log.

ResetDatabase is one autocomplete away from disaster. It's in the same Reqs namespace as everything else. Guard it: only allow it from a dedicated Artisan command that checks app()->environment(['local', 'testing']) or requires an explicit --force, and never expose it via a route.

Filters are a string DSL — build them, don't concatenate them. "'price' > context_item[\"price\"]" is ReQL-ish syntax with its own quoting rules. Interpolating user input into a filter is both a correctness bug and an injection surface. Wrap filter construction in small helper methods with typed parameters, and validate numeric values before they go in.

Eventual consistency is real. An item you just SetItemValues'd may not appear in recommendations for a short window, and a brand-new user with one interaction may get poor or empty results. Don't write tests asserting "item appears immediately." For cold start, either pass cascadeCreate/logic options, seed user properties in SetUserValues on registration (so the recommender has something), or fall back to non-personalised scenario results for users below an interaction threshold.

Batch responses need inspection, not blind trust. A Batch returns an array of per-request results in order. Partial failures are possible; iterate and log the entries that look like errors rather than assuming the whole batch landed. This is exactly why ingestion should be a queue job with retry semantics rather than a foreach in a controller.

Property schema is immutable-ish in practice. You can't change a property's declared type later without deleting and re-adding it (losing the values). Decide price is double and in_stock_from is timestamp on day one. Write the AddItemProperty calls into a versioned Artisan command so a new environment can be bootstrapped from zero.

Timestamps: pass DateTime objects and normalise to UTC. The client accepts DateTime instances (see the README). If your app runs Europe/Prague and Recombee stores UTC (or vice versa), time-decay boosting quietly misbehaves. Use now()->utc() and keep one conversion point.

Region selection is a latency decision, not a preference. us-west / eu-west / ap-se — pick the one nearest your app servers, not your office. A wrong region adds ~150–200ms round-trip to every read-path call and is the first thing to check when someone reports "recommendations feel slow."

Private token stays server-side. There are separate public/client tokens for browser and mobile SDKs. Never put the private token in a Blade template, a /config JSON endpoint, or a front-end bundle. If you must render recommendations client-side, generate them server-side through this SDK and pass the results.

returnProperties saves you a query. Recommendation calls can return item properties alongside IDs, which lets you build the response payload without a second whereIn against your own DB. Useful when your catalog is small and the recommendation rail doesn't need fresh pricing; skip it when prices are volatile and cached props would go stale.

Search is not a search-engine replacement. SearchItems gives you personalised full-text results from the Recombee catalog. It's excellent for "search results ordered by what this user will like," less so for facets, typos, and fuzzy matching. Common pattern: use your existing search (Scout/Meilisearch) to get candidate IDs, and Recombee for ranking/personalisation — or accept SearchItems for a fast v1 and revisit.

Octane / long-running workers: verify the singleton is stateless. A Client bound as a singleton is fine under Octane and Horizon as long as your wrapper holds no per-request state. Don't stash "current user" on the recommender service; pass it as an argument.

Pin the version and watch the changelog. The SDK is generated from Recombee's API spec, so minor versions can add request classes and options. ^6.2 is a sane constraint; read release notes when bumping in case an option you rely on (e.g. logic, diversity, rotationRate) changes shape.

Extension point to remember: when the SDK is missing an operation you need, don't fork it — check whether the underlying endpoint already has a Reqs\* class you haven't noticed (src/Requests/ is large), and if genuinely absent, extend Recombee\RecommApi\Requests\Request with the correct path and method rather than hand-rolling cURL. That keeps timeout/retry/exception behaviour consistent with everything else.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource