<?php
namespace App\Service;
use App\Entity\Product;
use App\Entity\Shop;
use App\Entity\User;
use App\Entity\UserProductView;
use App\Repository\ProductRepository;
use App\Repository\ShopRepository;
use App\Repository\UserProductViewRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class ViewTrackingService
{
private EntityManagerInterface $entityManager;
private RequestStack $requestStack;
private ProductRepository $productRepository;
private ShopRepository $shopRepository;
private UserProductViewRepository $userProductViewRepository;
public function __construct(
EntityManagerInterface $entityManager,
RequestStack $requestStack,
ProductRepository $productRepository,
ShopRepository $shopRepository,
UserProductViewRepository $userProductViewRepository
) {
$this->entityManager = $entityManager;
$this->requestStack = $requestStack;
$this->productRepository = $productRepository;
$this->shopRepository = $shopRepository;
$this->userProductViewRepository = $userProductViewRepository;
}
/**
* Incrémente le compteur de vues d'un produit
*/
public function trackProductView(Product $product, ?User $user = null): void
{
// Vérifier si cette vue a déjà été comptée pour cette session
if ($this->hasViewedProduct($product)) {
return;
}
// Incrémenter le compteur de vues
$product->setViewCount(($product->getViewCount() ?? 0) + 1);
// Incrémenter aussi les vues de la boutique
if ($product->getShop()) {
$product->getShop()->incrementViewCount();
}
// Enregistrer l'historique pour les utilisateurs connectés
if ($user) {
$this->recordUserProductView($user, $product);
}
$this->entityManager->flush();
// Marquer comme vue dans la session
$this->markProductAsViewed($product);
}
/**
* Enregistre une vue de produit pour un utilisateur
*/
private function recordUserProductView(User $user, Product $product): void
{
// Vérifier si l'utilisateur a déjà vu ce produit récemment (dans les 5 dernières minutes)
// pour éviter les doublons
$recentView = $this->userProductViewRepository->createQueryBuilder('upv')
->where('upv.user = :user')
->andWhere('upv.product = :product')
->andWhere('upv.viewedAt > :recentTime')
->setParameter('user', $user)
->setParameter('product', $product)
->setParameter('recentTime', new \DateTimeImmutable('-5 minutes'))
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if ($recentView) {
return; // Vue déjà enregistrée récemment
}
// Créer une nouvelle entrée
$userProductView = new UserProductView();
$userProductView->setUser($user);
$userProductView->setProduct($product);
$userProductView->setViewedAt(new \DateTimeImmutable());
$this->entityManager->persist($userProductView);
}
/**
* Incrémente le compteur de vues d'une boutique
*/
public function trackShopView(Shop $shop): void
{
// Vérifier si cette vue a déjà été comptée pour cette session
if ($this->hasViewedShop($shop)) {
return;
}
// Incrémenter le compteur de vues
$shop->incrementViewCount();
$this->entityManager->flush();
// Marquer comme vue dans la session
$this->markShopAsViewed($shop);
}
/**
* Vérifie si un produit a déjà été vu dans cette session
*/
private function hasViewedProduct(Product $product): bool
{
$session = $this->requestStack->getSession();
$viewedProducts = $session->get('viewed_products', []);
return in_array($product->getId(), $viewedProducts);
}
/**
* Vérifie si une boutique a déjà été vue dans cette session
*/
private function hasViewedShop(Shop $shop): bool
{
$session = $this->requestStack->getSession();
$viewedShops = $session->get('viewed_shops', []);
return in_array($shop->getId(), $viewedShops);
}
/**
* Marque un produit comme vu dans la session
*/
private function markProductAsViewed(Product $product): void
{
$session = $this->requestStack->getSession();
$viewedProducts = $session->get('viewed_products', []);
$viewedProducts[] = $product->getId();
// Limiter à 100 produits vus pour éviter une session trop lourde
if (count($viewedProducts) > 100) {
$viewedProducts = array_slice($viewedProducts, -100);
}
$session->set('viewed_products', $viewedProducts);
}
/**
* Marque une boutique comme vue dans la session
*/
private function markShopAsViewed(Shop $shop): void
{
$session = $this->requestStack->getSession();
$viewedShops = $session->get('viewed_shops', []);
$viewedShops[] = $shop->getId();
// Limiter à 50 boutiques vues pour éviter une session trop lourde
if (count($viewedShops) > 50) {
$viewedShops = array_slice($viewedShops, -50);
}
$session->set('viewed_shops', $viewedShops);
}
/**
* Récupère les produits récemment vus
*/
public function getRecentlyViewedProducts(int $limit = 10): array
{
$session = $this->requestStack->getSession();
$viewedProductIds = $session->get('viewed_products', []);
if (empty($viewedProductIds)) {
return [];
}
// Récupérer les derniers produits vus (ordre inverse)
$viewedProductIds = array_reverse($viewedProductIds);
$viewedProductIds = array_slice($viewedProductIds, 0, $limit);
return $this->productRepository->findBy(['id' => $viewedProductIds]);
}
/**
* Récupère les boutiques récemment vues
*/
public function getRecentlyViewedShops(int $limit = 5): array
{
$session = $this->requestStack->getSession();
$viewedShopIds = $session->get('viewed_shops', []);
if (empty($viewedShopIds)) {
return [];
}
// Récupérer les dernières boutiques vues (ordre inverse)
$viewedShopIds = array_reverse($viewedShopIds);
$viewedShopIds = array_slice($viewedShopIds, 0, $limit);
return $this->shopRepository->findBy(['id' => $viewedShopIds]);
}
/**
* Nettoie l'historique des vues
*/
public function clearViewHistory(): void
{
$session = $this->requestStack->getSession();
$session->remove('viewed_products');
$session->remove('viewed_shops');
}
/**
* Récupère les statistiques de vues pour un produit
*/
public function getProductViewStats(Product $product): array
{
return [
'totalViews' => $product->getViewCount() ?? 0,
'salesCount' => $product->getSalesCount() ?? 0,
'conversionRate' => $this->calculateConversionRate($product),
'averageRating' => $product->getAverageRating() ?? 0,
'reviewCount' => $product->getReviewCount() ?? 0,
];
}
/**
* Récupère les statistiques de vues pour une boutique
*/
public function getShopViewStats(Shop $shop): array
{
$totalProductViews = 0;
$totalProductSales = 0;
foreach ($shop->getProducts() as $product) {
$totalProductViews += $product->getViewCount() ?? 0;
$totalProductSales += $product->getSalesCount() ?? 0;
}
return [
'shopViews' => $shop->getViewCount() ?? 0,
'totalProductViews' => $totalProductViews,
'totalProductSales' => $totalProductSales,
'averageRating' => $shop->getAverageRating() ?? 0,
'reviewCount' => $shop->getReviewCount() ?? 0,
'activeProducts' => $shop->getActiveProductsCount(),
'conversionRate' => $this->calculateShopConversionRate($shop),
];
}
/**
* Calcule le taux de conversion d'un produit
*/
private function calculateConversionRate(Product $product): float
{
$views = $product->getViewCount() ?? 0;
$sales = $product->getSalesCount() ?? 0;
if ($views === 0) {
return 0.0;
}
return round(($sales / $views) * 100, 2);
}
/**
* Calcule le taux de conversion d'une boutique
*/
private function calculateShopConversionRate(Shop $shop): float
{
$totalViews = 0;
$totalSales = 0;
foreach ($shop->getProducts() as $product) {
$totalViews += $product->getViewCount() ?? 0;
$totalSales += $product->getSalesCount() ?? 0;
}
if ($totalViews === 0) {
return 0.0;
}
return round(($totalSales / $totalViews) * 100, 2);
}
}