Passed
Push — develop ( 9c991d...498af7 )
by BENARD
02:44
created

GetStats::getStats()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
rs 10
cc 3
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProjetNormandie\ForumBundle\Controller;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use ProjetNormandie\ForumBundle\ValueObject\ForumStatus;
9
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
10
use Symfony\Component\HttpFoundation\JsonResponse;
11
use Symfony\Component\HttpFoundation\Request;
12
use Symfony\Contracts\Cache\CacheInterface;
13
use Symfony\Contracts\Cache\ItemInterface;
14
15
class GetStats extends AbstractController
16
{
17
    public function __construct(
18
        private EntityManagerInterface $em,
19
        private ?CacheInterface $cache = null
20
    ) {
21
    }
22
23
    /**
24
     * Retourne les statistiques globales du forum
25
     */
26
    public function __invoke(Request $request): JsonResponse
27
    {
28
        $extended = $request->query->getBoolean('extended', false);
29
        $forceRefresh = $request->query->getBoolean('refresh', false);
30
31
        if ($extended) {
32
            $stats = $this->getExtendedStats($forceRefresh);
33
        } else {
34
            $stats = $this->getStats($forceRefresh);
35
        }
36
37
        return new JsonResponse($stats);
38
    }
39
40
    /**
41
     * Récupère les statistiques du forum avec mise en cache
42
     */
43
    public function getStats(bool $forceRefresh = false): array
44
    {
45
        if (!$this->cache || $forceRefresh) {
46
            return $this->calculateStats();
47
        }
48
49
        return $this->cache->get('forum_stats', function (ItemInterface $item) {
50
            $item->expiresAfter(300); // Cache pendant 5 minutes
51
            return $this->calculateStats();
52
        });
53
    }
54
55
    /**
56
     * Calcule les statistiques du forum
57
     */
58
    private function calculateStats(): array
59
    {
60
        $now = new \DateTime();
61
        $yesterday = (clone $now)->modify('-1 day');
62
        $today = (clone $now)->setTime(0, 0, 0);
63
64
        return [
65
            'nbForum' => $this->countForums(),
66
            'nbTopic' => $this->countTopics(),
67
            'nbMessage' => $this->countMessages(),
68
            'activeUsers' => $this->countActiveUsers($yesterday),
69
            'todayActivity' => [
70
                'nbNewTopic' => $this->countNewTopicsToday($today),
71
                'nbNewMessage' => $this->countNewMessagesToday($today),
72
            ],
73
            'lastUpdate' => $now->format('c'),
74
        ];
75
    }
76
77
    /**
78
     * Invalide le cache des statistiques
79
     */
80
    public function invalidateCache(): void
81
    {
82
        if ($this->cache) {
83
            $this->cache->delete('forum_stats');
84
            $this->cache->delete('forum_stats_extended');
85
        }
86
    }
87
88
    /**
89
     * Compte le nombre total de forums publics
90
     */
91
    private function countForums(): int
92
    {
93
        try {
94
            $query = $this->em->createQueryBuilder()
95
                ->select('COUNT(f.id)')
96
                ->from('ProjetNormandie\ForumBundle\Entity\Forum', 'f')
97
                ->where('f.status = :status')
98
                ->setParameter('status', ForumStatus::PUBLIC);
99
100
            return (int) $query->getQuery()->getSingleScalarResult();
101
        } catch (\Exception) {
102
            return 0;
103
        }
104
    }
105
106
    /**
107
     * Compte le nombre total de topics non archivés dans les forums publics
108
     */
109
    private function countTopics(): int
110
    {
111
        try {
112
            $query = $this->em->createQueryBuilder()
113
                ->select('COUNT(t.id)')
114
                ->from('ProjetNormandie\ForumBundle\Entity\Topic', 't')
115
                ->join('t.forum', 'f')
116
                ->where('f.status = :status')
117
                ->andWhere('t.boolArchive = :archived')
118
                ->setParameter('status', ForumStatus::PUBLIC)
119
                ->setParameter('archived', false);
120
121
            return (int) $query->getQuery()->getSingleScalarResult();
122
        } catch (\Exception) {
123
            return 0;
124
        }
125
    }
126
127
    /**
128
     * Compte le nombre total de messages dans les forums publics
129
     */
130
    private function countMessages(): int
131
    {
132
        try {
133
            $query = $this->em->createQueryBuilder()
134
                ->select('COUNT(m.id)')
135
                ->from('ProjetNormandie\ForumBundle\Entity\Message', 'm')
136
                ->join('m.topic', 't')
137
                ->join('t.forum', 'f')
138
                ->where('f.status = :status')
139
                ->setParameter('status', ForumStatus::PUBLIC);
140
141
            return (int) $query->getQuery()->getSingleScalarResult();
142
        } catch (\Exception) {
143
            return 0;
144
        }
145
    }
146
147
    /**
148
     * Compte le nombre d'utilisateurs actifs dans les dernières 24h
149
     */
150
    private function countActiveUsers(\DateTime $since): int
151
    {
152
        try {
153
            // Approche optimisée avec UNION pour éviter les doublons
154
            $sql = "
155
                SELECT COUNT(DISTINCT user_id) as count FROM (
156
                    SELECT user_id FROM pnf_topic_user_last_visit 
157
                    WHERE last_visited_at >= :since
158
                    UNION
159
                    SELECT user_id FROM pnf_forum_user_last_visit 
160
                    WHERE last_visited_at >= :since
161
                ) AS active_users
162
            ";
163
164
            $stmt = $this->em->getConnection()->prepare($sql);
165
            $result = $stmt->executeQuery(['since' => $since->format('Y-m-d H:i:s')]);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Statement::executeQuery() has too many arguments starting with array('since' => $since->format('Y-m-d H:i:s')). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

165
            /** @scrutinizer ignore-call */ 
166
            $result = $stmt->executeQuery(['since' => $since->format('Y-m-d H:i:s')]);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
166
167
            return (int) $result->fetchOne();
168
        } catch (\Exception) {
169
            return 0;
170
        }
171
    }
172
173
    /**
174
     * Compte le nombre de nouveaux topics créés aujourd'hui
175
     */
176
    private function countNewTopicsToday(\DateTime $today): int
177
    {
178
        try {
179
            $query = $this->em->createQueryBuilder()
180
                ->select('COUNT(t.id)')
181
                ->from('ProjetNormandie\ForumBundle\Entity\Topic', 't')
182
                ->join('t.forum', 'f')
183
                ->where('t.createdAt >= :today')
184
                ->andWhere('f.status = :status')
185
                ->setParameter('today', $today)
186
                ->setParameter('status', ForumStatus::PUBLIC);
187
188
            return (int) $query->getQuery()->getSingleScalarResult();
189
        } catch (\Exception) {
190
            return 0;
191
        }
192
    }
193
194
    /**
195
     * Compte le nombre de nouveaux messages créés aujourd'hui
196
     */
197
    private function countNewMessagesToday(\DateTime $today): int
198
    {
199
        try {
200
            $query = $this->em->createQueryBuilder()
201
                ->select('COUNT(m.id)')
202
                ->from('ProjetNormandie\ForumBundle\Entity\Message', 'm')
203
                ->join('m.topic', 't')
204
                ->join('t.forum', 'f')
205
                ->where('m.createdAt >= :today')
206
                ->andWhere('f.status = :status')
207
                ->setParameter('today', $today)
208
                ->setParameter('status', ForumStatus::PUBLIC);
209
210
            return (int) $query->getQuery()->getSingleScalarResult();
211
        } catch (\Exception) {
212
            return 0;
213
        }
214
    }
215
216
    /**
217
     * Récupère des statistiques étendues avec plus de détails
218
     */
219
    public function getExtendedStats(bool $forceRefresh = false): array
220
    {
221
        if (!$this->cache || $forceRefresh) {
222
            return $this->calculateExtendedStats();
223
        }
224
225
        return $this->cache->get('forum_stats_extended', function (ItemInterface $item) {
226
            $item->expiresAfter(300); // Cache pendant 5 minutes
227
            return $this->calculateExtendedStats();
228
        });
229
    }
230
231
    /**
232
     * Calcule les statistiques étendues
233
     */
234
    private function calculateExtendedStats(): array
235
    {
236
        $baseStats = $this->calculateStats();
237
238
        return array_merge($baseStats, [
239
            'weekActivity' => $this->getWeekActivity(),
240
            'topActiveUsers' => $this->getTopActiveUsers(),
241
            'forumBreakdown' => $this->getForumBreakdown(),
242
        ]);
243
    }
244
245
    /**
246
     * Récupère l'activité de la semaine
247
     */
248
    private function getWeekActivity(): array
249
    {
250
        $weekAgo = (new \DateTime())->modify('-7 days');
251
252
        try {
253
            $topicQuery = $this->em->createQueryBuilder()
254
                ->select('COUNT(t.id)')
255
                ->from('ProjetNormandie\ForumBundle\Entity\Topic', 't')
256
                ->join('t.forum', 'f')
257
                ->where('t.createdAt >= :weekAgo')
258
                ->andWhere('f.status = :status')
259
                ->setParameter('weekAgo', $weekAgo)
260
                ->setParameter('status', ForumStatus::PUBLIC);
261
262
            $messageQuery = $this->em->createQueryBuilder()
263
                ->select('COUNT(m.id)')
264
                ->from('ProjetNormandie\ForumBundle\Entity\Message', 'm')
265
                ->join('m.topic', 't')
266
                ->join('t.forum', 'f')
267
                ->where('m.createdAt >= :weekAgo')
268
                ->andWhere('f.status = :status')
269
                ->setParameter('weekAgo', $weekAgo)
270
                ->setParameter('status', ForumStatus::PUBLIC);
271
272
            return [
273
                'nbNewTopicWeek' => (int) $topicQuery->getQuery()->getSingleScalarResult(),
274
                'nbNewMessageWeek' => (int) $messageQuery->getQuery()->getSingleScalarResult(),
275
            ];
276
        } catch (\Exception) {
277
            return [
278
                'nbNewTopicWeek' => 0,
279
                'nbNewMessageWeek' => 0,
280
            ];
281
        }
282
    }
283
284
    /**
285
     * Récupère les utilisateurs les plus actifs (dernières 24h)
286
     */
287
    private function getTopActiveUsers(int $limit = 5): array
0 ignored issues
show
Unused Code introduced by
The parameter $limit is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

287
    private function getTopActiveUsers(/** @scrutinizer ignore-unused */ int $limit = 5): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
288
    {
289
        $yesterday = (new \DateTime())->modify('-1 day');
0 ignored issues
show
Unused Code introduced by
The assignment to $yesterday is dead and can be removed.
Loading history...
290
291
        try {
292
            $sql = "
293
                SELECT user_id, COUNT(*) as activity_count FROM (
294
                    SELECT user_id FROM pnf_topic_user_last_visit 
295
                    WHERE last_visited_at >= :since
296
                    UNION ALL
297
                    SELECT user_id FROM pnf_forum_user_last_visit 
298
                    WHERE last_visited_at >= :since
299
                ) AS activities
300
                GROUP BY user_id
301
                ORDER BY activity_count DESC
302
                LIMIT :limit
303
            ";
304
305
            $stmt = $this->em->getConnection()->prepare($sql);
306
            $result = $stmt->executeQuery();
307
308
            return $result->fetchAllAssociative();
309
        } catch (\Exception) {
310
            return [];
311
        }
312
    }
313
314
    /**
315
     * Récupère une répartition par forum
316
     */
317
    private function getForumBreakdown(): array
318
    {
319
        try {
320
            $query = $this->em->createQueryBuilder()
321
                ->select('f.id, f.libForum as name, f.slug, f.nbTopic, f.nbMessage')
322
                ->from('ProjetNormandie\ForumBundle\Entity\Forum', 'f')
323
                ->where('f.status = :status')
324
                ->orderBy('f.nbMessage', 'DESC')
325
                ->setMaxResults(10)
326
                ->setParameter('status', ForumStatus::PUBLIC);
327
328
            return $query->getQuery()->getResult();
329
        } catch (\Exception) {
330
            return [];
331
        }
332
    }
333
}
334
335