umutphp/laravel-model-recommendation
A Laravel package that adds recommendation capabilities to your Eloquent models. Track user interactions, then retrieve suggested models using built-in algorithms—ideal for powering "you may also like" features with a simple, expressive API. Includes migrations and traits to get started fa...
The package provides model-to-model recommendations (think "related posts", "you may also like") by scoring how similar two Eloquent models are and persisting the results, so serving a recommendation list is a simple relation lookup rather than a query-time computation.
Minimal setup:
composer require umutphp/laravel-model-recommendation — as of v0.4.0 this resolves against Laravel 10, 11 and 12 (v0.3.x stopped at 11), so it is safe to pull into a Laravel 12 app.php artisan vendor:publish and select the package's provider/tag) to get the config file and the recommendations migration, then run php artisan migrate.php artisan list for the recommendation:* entry) to backfill scores, then read them back through the relation the trait adds to the model.Look first at:
--help output, since the flags (model filter, truncate/rebuild, chunk size) determine how you wire it into a scheduler.First real use case: a Post model with tags. Expose the tag IDs from the similarity method, run generation once, and render $post->recommendations in a sidebar. That gives you a working end-to-end loop in under ten minutes and surfaces immediately whether your data is dense enough for the scores to be meaningful.
Pick your similarity source deliberately. The quality of everything downstream is determined by what the trait's similarity method returns. Tags/categories are the easy path; for content models without tags, synthesise the signal (e.g. derive a tag set from category + author + keyword extraction) rather than feeding in raw free text that will never overlap.
Generate out-of-band, never on save. The generation pass compares models against each other, so it is O(n²)-ish and absolutely should not live in an updated observer. Schedule it — nightly for small datasets, hourly with a chunked/limited model scope for large ones — and treat the recommendations table as a materialised view.
Read through the relation with a limit and eager load. Serve $post->recommendations()->limit(5)->get() (or the configured limit) and eager load nested relations the card component needs. Because the rows are precomputed, this is a cheap indexed lookup; put a short cache TTL on the rendered list if the page is hot.
Stale-row hygiene. When a model is deleted or its tag set changes materially, its recommendations and the rows pointing at it go stale. Pair the generation run with a cleanup step, and prefer a full rebuild for the affected model over incremental patching unless your dataset is large enough that the full pass hurts.
Multi-tenant / per-user recommendations. The default flow is global per-model similarity. For personalised lists, keep the package output as the candidate pool and apply your own user-level filtering or re-ranking on top, rather than trying to force user context into the package's scoring.
Testing. Use RefreshDatabase with an explicit Relation::enforceMorphMap() in tests so the polymorphic model_type/recommended_type columns are stable, and assert on the relation's contents after running the generation command inside the test rather than mocking the scorer.
composer update; there are no documented breaking changes or deprecations. If you are on Laravel 12 this is the first usable version, so pin ^0.4.conflict or a pinned 0.3.* in composer.json, remove it, otherwise the new illuminate/support range will not resolve.Relation::enforceMorphMap(), do it before generating, or previously stored class-name strings will no longer resolve.withoutOverlapping() on the scheduled task).How can I help you explore Laravel packages today?