amranidev/laracombee
Laracombee is a Laravel package offering a simple, Laravel-friendly interface to the Recombee recommendation API. Manage users, items, interactions, and personalized recommendations for e-commerce, media, and marketplace applications.
Install and let package discovery register the provider:
composer require amranidev/laracombee
php artisan vendor:publish --tag=laracombee-config
Then set the minimum config in config/laracombee.php (or .env):
'database' => env('RECOMBEE_DATABASE'),
'token' => env('RECOMBEE_TOKEN'),
'protocol' => 'https', // default is http — change this
'region' => 'eu-west',
'timeout' => 2000,
'user' => \App\Models\User::class,
'item' => \App\Models\Book::class,
The user and item keys matter: the Artisan commands (laracombee:migrate, laracombee:seed, etc.) resolve these models. Next, declare the schema on each model with the static $laracombee property — this is the source of truth for Recombee properties:
public static $laracombee = [
'name' => 'string',
'age' => 'int',
];
Then push the schema and backfill existing rows:
php artisan laracombee:migrate user
php artisan laracombee:migrate item
php artisan laracombee:seed item --chunk=250
First real use case: hydrate a recommendation list for a known user.
$user = User::findOrFail($id);
$response = Laracombee::recommendTo($user, 10)->wait();
$ids = collect($response['recomms'])->pluck('id');
$books = Book::whereIn('id', $ids)->get();
Once that works, add the write side — fire addDetailView / addPurchase / addRating as users interact, otherwise recommendations are just popular-items noise.
Wrap the client in your own service. Laracombee is a thin facade over the Recombee SDK; don't scatter Laracombee:: calls across controllers. Create App\Services\Recommender that owns recommendation fetching, fallbacks, and caching. It makes tests trivial and lets you swap engines later.
Read calls execute immediately; write calls return a request you must send. This asymmetry is the single most important pattern:
// Write: build request, then send + handle + block
Laracombee::send(Laracombee::addPurchase($userId, $itemId))
->then(fn () => null)
->otherwise(fn ($e) => Log::warning('recombee', ['e' => $e]))
->wait();
// Read: already dispatched, just wait
$response = Laracombee::recommendTo($user, 10, ['scenario' => 'homepage'])->wait();
Track interactions from domain events, not controllers. Register listeners or model observers and push to Recombee off the request path:
// app/Observers/OrderObserver.php
public function created(Order $order): void
{
dispatch(new SyncPurchaseToRecombee($order->user_id, $order->book_id));
}
In the job, use cascadeCreate => true in the options array so a purchase from a user not yet in Recombee still lands:
Laracombee::addPurchase($userId, $itemId, ['cascadeCreate' => true, 'timestamp' => now()->toIso8601String()]);
Batch anything bulk. Backfills, imports, and re-syncs should use addUsers / batch rather than N individual wait() calls:
$requests = $books->map(fn ($b) => Laracombee::setItemValues($b->id, [
'title' => $b->title,
'price' => (float) $b->price,
]));
Laracombee::batch($requests->all())->wait();
Chunk to a few hundred requests per batch and run it in a queued job; large synchronous batches will blow past the timeout setting.
Treat $laracombee as a migration file. Add properties to the static array, then run laracombee:migrate item as part of deploy. For one-off changes, use the CLI instead of editing the model:
php artisan laracombee:add isbn:string published_at:timestamp --to=item
php artisan laracombee:drop isbn --from=item
Customize the recommendation request per surface. Pass a scenario and ask for properties back to avoid an N+1 on hydration:
$response = Laracombee::recommendTo($user, 12, [
'scenario' => 'product_detail',
'returnProperties' => ['title', 'price', 'image'],
'filter' => "'price' < 50",
]);
Multiple databases. For a separate reporting or staging Recombee DB, scaffold a class and use it directly (or bind it in a service provider):
php artisan laracombee:new ReportingLaracombee
$reporting = app(\App\Laracombee\ReportingLaracombee::class);
$reporting->recommendItemsToUser($id, 5)->wait();
Extend AbstractRecombee when you need real control — centralised retries, metrics, or error translation:
class MyRecombee extends AbstractRecombee
{
public function send(Request $request)
{
return $this->client->send($request)
->then(null, function ($e) {
report($e);
throw $e;
});
}
}
->wait() silently drops the request. The SDK is async; a promise that's never awaited may never complete, and exceptions in otherwise never fire. In PHP-FPM this is the #1 source of "why is my data missing" bugs. Always terminate the chain with wait() on writes.send() on a recommendation. recommendTo, recommendItemsToUser, and recommendUsersToUser dispatch immediately and return a promise. Wrapping them in send() will fail or double-send.protocol is http. Set https in production; the private token travels in the request path/headers and you don't want it on the wire in plaintext.timeout defaults to 2000 ms. Fine for a single interaction, too tight for batches or seeding. Raise it in config for queue workers and console commands.laracombee:reset is a full database wipe — users, items, properties, interactions, series. Guard it: never expose it as a deploy step, and consider blocking it in production via a check in your deploy script or an environment gate.$laracombee must match what you actually send. Sending "42" for an int property or a string date for a timestamp leads to rejected requests that are easy to miss inside a batch. Cast explicitly in your sync jobs.laracombee:drop explicitly. Stale properties accumulate silently.cascadeCreate on interaction writes for anything that could arrive before the user/item sync (signups, guest checkouts, rapid imports). Otherwise you get "user not found" errors that look like package failures.addUser vs setUserValues vs mergeUsersWithId. addUser creates; setUserValues updates fields on an existing user; mergeUsersWithId folds an anonymous/guest ID into a real one — the standard pattern for "guest browses, then logs in."recomms. New users, cold items, and over-restrictive filters all return an empty array. Always have a fallback (popular items, recent items) rather than an empty shelf.php artisan config:cache, edits to config/laracombee.php won't take effect until you re-cache. This bites people during initial setup when they're toggling the database ID.laracombee:new is not auto-bound to the container. Instantiate it directly or add an explicit binding in a service provider — don't assume Laracombee::class resolves to it.otherwise failures, and let recommendations degrade gracefully.composer.json, keep all direct Laracombee:: usage behind your own service class, and cover it with a fake/offline test so an upstream SDK bump is a one-file fix.How can I help you explore Laravel packages today?