cyrildewit/eloquent-viewable
Track and query page views on Eloquent models without external analytics. Record views with optional cooldown, count totals/unique views, filter by date periods, order models by views, and ignore crawlers. Stores each view as a DB record.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require cyrildewit/eloquent-viewable
php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="migrations"
php artisan migrate
(Optional: Publish config with --tag="config")
Model Integration:
Add InteractsWithViews trait and implement Viewable interface to your Eloquent model:
use CyrildeWit\EloquentViewable\InteractsWithViews;
use CyrildeWit\EloquentViewable\Contracts\Viewable;
class Post extends Model implements Viewable
{
use InteractsWithViews;
}
First Use Case: Track views in a controller:
public function show(Post $post)
{
views($post)->record(); // Record view
return view('post.show', compact('post'));
}
Retrieve counts:
$totalViews = views($post)->count();
$uniqueViews = views($post)->unique()->count();
View Recording:
views($model)->record();views($model)->cooldown(now()->addHours(2))->record();views($model)->collection('premium')->record();Querying Views:
views($post)->period(Period::pastDays(7))->count();views($post)->unique()->count();views(new Post())->count();Model Ordering:
// Order posts by total views (descending)
Post::orderByViews()->get();
// Order by unique views in last 30 days
Post::orderByUniqueViews('asc', Period::pastDays(30))->get();
Caching:
// Cache for 1 hour
views($post)->remember(3600)->count();
// Cache until specific date
views($post)->remember(now()->addWeek())->count();
public function handle($request, Closure $next)
{
views($request->route('post'))->record();
return $next($request);
}
return response()->json([
'post' => $post,
'views' => views($post)->count(),
'unique_views' => views($post)->unique()->count()
]);
$trending = Post::orderByViews('desc', Period::pastHours(24))->take(5)->get();
Crawler Filtering:
config(['eloquent-viewable.ignore_crawlers' => false]);
config/eloquent-viewable.php:
'ignored_ips' => ['127.0.0.1', '192.168.1.*'],
Database Bloat:
views table:
Schema::table('views', function (Blueprint $table) {
$table->index(['viewable_type', 'viewable_id', 'visitor']);
});
visitor column indexing.Cooldown Misuse:
views($post)->cooldown(now()->addDays(7))->record();
Caching Caveats:
// ❌ Avoid (dynamic period)
views($post)->period(Period::since($userInputDate))->remember()->count();
// ✅ Better (static period)
views($post)->period(Period::pastDays(7))->remember()->count();
View Records: Inspect raw records:
$views = \CyrildeWit\EloquentViewable\View::where('viewable_id', $post->id)->get();
Visitor Tracking:
Check visitor identification logic in Visitor class. Override if needed:
// config/eloquent-viewable.php
'visitor_resolver' => \App\Services\CustomVisitorResolver::class;
Performance:
remember() for static queries.unique_views_count column).Custom Visitor Data:
Extend Visitor model or override resolver:
// app/Providers/EloquentViewableServiceProvider.php
public function boot()
{
\CyrildeWit\EloquentViewable\Visitor::addResolveCallback(function ($request) {
return $request->user() ? $request->user()->id : $request->ip();
});
}
Custom View Model:
Replace View model by binding your own in the service provider:
$this->app->bind(
\CyrildeWit\EloquentViewable\Contracts\View::class,
\App\Models\CustomView::class
);
Crawler Detection:
Replace CrawlerDetectAdapter:
$this->app->bind(
\CyrildeWit\EloquentViewable\Contracts\CrawlerDetect::class,
\App\Services\CustomCrawlerDetector::class
);
Macros:
Add custom methods to Views facade:
\CyrildeWit\EloquentViewable\Views::macro('trending', function () {
return $this->period(Period::pastHours(24))->orderBy('created_at', 'desc');
});
Usage:
views($post)->trending()->count();
// app/Console/Commands/UpdateViewCounts.php
public function handle()
{
Post::chunk(100, function ($posts) {
foreach ($posts as $post) {
$post->unique_views_count = views($post)->unique()->count();
$post->save();
}
});
}
SoftDeletes trait for graceful view cleanup:
class Post extends Model implements Viewable
{
use InteractsWithViews, SoftDeletes;
}
$recentViews = \CyrildeWit\EloquentViewable\View::where('created_at', '>', now()->subHours(1))
->with('viewable')
->get();
How can I help you explore Laravel packages today?