<?phpnamespace App\Entity\Cvs;use Doctrine\DBAL\Types\Types;use Doctrine\ORM\Mapping as ORM;/** * Statistiques de recherche : 1 ligne par combinaison unique * (normalizedQuery, cityKeyword, locale) sur une fenêtre glissante de 24h. * * Lorsqu'une recherche est faite : * - Si une ligne existe pour cette combinaison ET lastSeenAt est dans les 24h * → on incrémente views et on met à jour lastSeenAt * - Sinon (pas de ligne OU dernière > 24h) → nouvelle ligne avec views=1 * * @ORM\Table("cvs_searchstats", indexes={ * @ORM\Index(name="idx_searchstats_combo", columns={"normalized_query", "city_keyword", "locale"}), * @ORM\Index(name="idx_searchstats_lastseen", columns={"last_seen_at"}), * @ORM\Index(name="idx_searchstats_views", columns={"views"}) * }) * @ORM\Entity(repositoryClass="App\Repository\Cvs\SearchStatsRepository") * @ORM\HasLifecycleCallbacks() */class SearchStats{ /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * Texte de la recherche (tel que tapé/envoyé par l'utilisateur). * * @ORM\Column(name="query", type="string", length=255, nullable=true) */ protected $query; /** * Version normalisée de la recherche (lowercase + trim + slugify partiel) * utilisée pour le matching (évite les doublons "Paris" / "paris" / " Paris "). * * @ORM\Column(name="normalized_query", type="string", length=255, nullable=true) */ protected $normalizedQuery; /** * Si la recherche a été faite depuis une page ville (ex: /jobs/paris), * stocke le keyword ville (label de JobsFilters). NULL si pas de contexte ville. * * @ORM\Column(name="city_keyword", type="string", length=255, nullable=true) */ protected $cityKeyword; /** * Langue (fr, en). * * @ORM\Column(name="locale", type="string", length=2, nullable=true) */ protected $locale; /** * Source de la recherche : 'submit' | 'landing_city' | 'landing_keyword'. * * @ORM\Column(name="source", type="string", length=32, nullable=true) */ protected $source; /** * Compteur d'occurrences pour cette combinaison dans la fenêtre 24h. * * @ORM\Column(name="views", type="integer", options={"default":1}) */ protected $views = 1; /** * Date de la 1ère vue dans la fenêtre courante. * * @ORM\Column(name="first_seen_at", type="datetime", nullable=true) */ protected $firstSeenAt; /** * Date de la dernière vue (utilisée pour le check "dans les 24h"). * * @ORM\Column(name="last_seen_at", type="datetime", nullable=true) */ protected $lastSeenAt; public function getId(): ?int { return $this->id; } public function getQuery(): ?string { return $this->query; } public function setQuery(?string $query): self { $this->query = $query; return $this; } public function getNormalizedQuery(): ?string { return $this->normalizedQuery; } public function setNormalizedQuery(?string $q): self { $this->normalizedQuery = $q; return $this; } public function getCityKeyword(): ?string { return $this->cityKeyword; } public function setCityKeyword(?string $c): self { $this->cityKeyword = $c; return $this; } public function getLocale(): ?string { return $this->locale; } public function setLocale(?string $l): self { $this->locale = $l; return $this; } public function getSource(): ?string { return $this->source; } public function setSource(?string $s): self { $this->source = $s; return $this; } public function getViews(): int { return $this->views ?? 0; } public function setViews(int $v): self { $this->views = $v; return $this; } public function incrementViews(): self { $this->views = ($this->views ?? 0) + 1; return $this; } public function getFirstSeenAt(): ?\DateTime { return $this->firstSeenAt; } public function setFirstSeenAt(?\DateTime $d): self { $this->firstSeenAt = $d; return $this; } public function getLastSeenAt(): ?\DateTime { return $this->lastSeenAt; } public function setLastSeenAt(?\DateTime $d): self { $this->lastSeenAt = $d; return $this; }}