Files
2026-04-18 17:02:56 +02:00

82 lines
2.5 KiB
PHP

<?php
namespace App\Services\Posts;
use App\Models\Post;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
/**
* Tracks post impressions (throttled per session) and computes engagement score.
*
* Impression throttle: 1 impression per post per session-key per hour.
* Engagement score: (reactions*2 + comments*3 + saves) / max(impressions, 1)
*/
class PostAnalyticsService
{
/**
* Record a post impression, throttled by a session key.
* Returns true if impression was counted, false if throttled.
*/
public function trackImpression(Post $post, string $sessionKey): bool
{
$cacheKey = "impression:{$post->id}:{$sessionKey}";
if (Cache::has($cacheKey)) {
return false; // already counted this hour
}
Cache::put($cacheKey, 1, now()->addHour());
Post::withoutTimestamps(function () use ($post) {
DB::table('posts')
->where('id', $post->id)
->increment('impressions_count');
});
// Recompute engagement score asynchronously via a quick DB update
$this->refreshEngagementScore($post->id);
return true;
}
/**
* Refresh the cached engagement_score = (reactions*2 + comments*3 + saves) / max(impressions, 1)
*/
public function refreshEngagementScore(int $postId): void
{
Post::withoutTimestamps(function () use ($postId) {
DB::table('posts')
->where('id', $postId)
->update([
'engagement_score' => DB::raw(
'(reactions_count * 2 + comments_count * 3 + saves_count) / GREATEST(impressions_count, 1)'
),
]);
});
}
/**
* Return analytics summary for a post (owner view).
*/
public function getSummary(Post $post): array
{
$reactions = $post->reactions_count;
$comments = $post->comments_count;
$saves = $post->saves_count;
$impressions = $post->impressions_count;
$rate = $impressions > 0
? round((($reactions + $comments + $saves) / $impressions) * 100, 2)
: 0.0;
return [
'impressions' => $impressions,
'reactions' => $reactions,
'comments' => $comments,
'saves' => $saves,
'engagement_rate' => $rate, // percentage
'engagement_score' => round($post->engagement_score, 4),
];
}
}