Parcourir la source

Refactorisation de la recherche de tags.

bastien il y a 13 ans
Parent
révision
8d6f6bef9f

+ 5 - 273
src/Muzich/CoreBundle/Controller/SearchController.php Voir le fichier

@@ -8,7 +8,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
8 8
 use Muzich\CoreBundle\Searcher\ElementSearcher;
9 9
 use Muzich\CoreBundle\Form\Search\ElementSearchForm;
10 10
 use Symfony\Component\HttpFoundation\Response;
11
-use Muzich\CoreBundle\Util\StrictCanonicalizer;
11
+use Muzich\CoreBundle\Util\TagLike;
12 12
 
13 13
 class SearchController extends Controller
14 14
 {
@@ -170,225 +170,7 @@ class SearchController extends Controller
170 170
     
171 171
     throw new \Exception('XmlHttpRequest only for this action');
172 172
   }
173
-  
174
-  /**
175
-   * Ajoute le tag au début du tableau passé en paramètre si celui-ci
176
-   * n'est pas a l'intérieur.
177
-   * 
178
-   * @param array $array
179
-   * @param Tag $tag
180
-   * @return array 
181
-   */
182
-  private function sort_addtop_if_isnt_in($array, $tag)
183
-  {
184
-    $in = false;
185
-    for ($x=0;$x<=sizeof($array)-1;$x++)
186
-    {
187
-      if ($array[$x]['id'] == $tag['id'])
188
-      {
189
-        $in = true;
190
-        break;
191
-      }
192
-    }
193
-    
194
-    if (!$in)
195
-    {
196
-      array_unshift($array, $tag);
197
-      return $array;
198
-    }
199
-    return $array;
200
-  }
201
-  
202
-  /**
203
-   * Ajoute le tag a al fin du tableau passé en paramètre si celui-ci
204
-   * n'est pas a l'intérieur.
205
-   * 
206
-   * @param array $array
207
-   * @param Tag $tag
208
-   * @return array 
209
-   */
210
-  private function sort_addbottom_if_isnt_in($array, $tag)
211
-  {
212
-    $in = false;
213
-    for ($x=0;$x<=sizeof($array)-1;$x++)
214
-    {
215
-      if ($array[$x]['id'] == $tag['id'])
216
-      {
217
-        $in = true;
218
-        break;
219
-      }
220
-    }
221
-    
222
-    if (!$in)
223
-    {
224
-      $array[] = $tag;
225
-      return $array;
226
-    }
227
-    return $array;
228
-  }
229
-  
230
-  /**
231
-   * Organise le trie des tags de manière plus friendly user.
232
-   *
233
-   * @param array $tags
234
-   * @param string $search
235
-   * @return array 
236
-   */
237
-  private function sort_search_tags($tags, $search)
238
-  {
239
-    $same_found = false;
240
-    $canonicalizer = new StrictCanonicalizer();
241
-    $tag_sorted = array();
242
-    
243
-    foreach ($tags as $i => $tag)
244
-    {
245
-      // Pas plus de trois caractères en plus de la recherche
246
-      $terms = array_merge(
247
-        explode(' ', $search), 
248
-        explode('-', $search), 
249
-        explode(',', $search)
250
-      );
251
-      foreach ($terms as $word)
252
-      {
253
-        $word = trim($word);
254
-        if (strlen($word) > 1)
255
-        {
256
-          if (
257
-            strlen(str_replace(strtoupper($canonicalizer->canonicalize($word)), '', strtoupper($tag['slug']))) < 4
258
-            && $word != $search
259
-          )
260
-          {
261
-            $tag_sorted = $this->sort_addtop_if_isnt_in($tag_sorted, $tag);
262
-          }
263
-        }
264
-      }
265
-      
266
-    }
267
-    
268
-    // Uniquement dans le cas de présence d'un espace/separateur ou plus
269
-    // on cherche les mot composé comme lui
270
-    $terms = array_merge(
271
-      explode(' ', $search), 
272
-      explode('-', $search), 
273
-      explode(',', $search)
274
-    );
275 173
     
276
-    $tags_counteds = array();
277
-    foreach ($tags as $i => $tag)
278
-    {
279
-      $terms_search = array_merge(
280
-        explode(' ', $tag['slug']), 
281
-        explode('-', $tag['slug']), 
282
-        explode(',', $tag['slug'])
283
-      );
284
-      
285
-      foreach ($terms as $word)
286
-      {
287
-        $word = trim($word);
288
-        if (
289
-          strpos(strtoupper($tag['slug']), strtoupper($word)) !== false
290
-          && count($terms_search) > 3
291
-        )
292
-        {
293
-          $count = 1;
294
-          if (array_key_exists($tag['id'], $tags_counteds))
295
-          {
296
-            $count = ($tags_counteds[$tag['id']]['count'])+1;
297
-          }
298
-          $tags_counteds[$tag['id']] = array(
299
-            'count' => $count,
300
-            'tag'   => $tag
301
-          );
302
-        }
303
-      }
304
-    }
305
-    
306
-    
307
-    foreach ($tags_counteds as $id => $counted)
308
-    {
309
-      if ($counted['count'] > 1)
310
-      {
311
-        // Ci-dessous on va chercher a voir si le tag et la recherche on le 
312
-        // même nombre de mots, si c'est le cas on pourra considérer cette 
313
-        // recherche comme lié a un tag connu.
314
-        
315
-        $words_search = array_merge(
316
-          explode(' ', $search), 
317
-          explode('-', $search), 
318
-          explode(',', $search)
319
-        );
320
-        $words_search = array_unique($words_search);
321
-        
322
-        $words_tag = array_merge(
323
-          explode(' ', $counted['tag']['slug']), 
324
-          explode('-', $counted['tag']['slug'])
325
-        );
326
-        $words_tag = array_unique($words_tag);
327
-            
328
-        if (count($words_search) == count($words_tag))
329
-        {
330
-          $same_found = true;
331
-        }
332
-        
333
-        // Cette verif permet de ne pas ajouter les tags qui n'ont qu'un mot
334
-        // Si on ajouté ce tag maintenant il ne serais pas ajouté au controle en dessous
335
-        // (nom identique) et donc pas au dessus.
336
-        $tag_sorted = $this->sort_addtop_if_isnt_in($tag_sorted, $counted['tag']);
337
-        
338
-      }
339
-    }
340
-    
341
-    foreach ($tags as $i => $tag)
342
-    {
343
-      // Chaine de caractère identique
344
-      $terms = array_merge(
345
-        array($search), 
346
-        explode(' ', $search), 
347
-        explode('-', $search),
348
-        explode(',', $search),
349
-        array(str_replace(' ', '-', $search)),
350
-        array(str_replace('-', ' ', $search))
351
-      );
352
-      
353
-      foreach ($terms as $word)
354
-      {
355
-        $word = trim($word);
356
-        if (strlen($word) > 1)
357
-        {
358
-          if (strtoupper($canonicalizer->canonicalize($word)) == strtoupper($tag['slug']))
359
-          {
360
-            // Ci-dessous on déduit si le mot est identique au tag . 
361
-            // De façon a ce que si c'est le cas, pouvoir dire:
362
-            // oui le terme recherché est connu.
363
-            if (in_array($word, array(
364
-              $search,
365
-              str_replace(' ', '-', $search),
366
-              str_replace('-', ' ', $search),
367
-              str_replace(',', ' ', $search),
368
-              str_replace(', ', '-', $search),
369
-              str_replace(',', ' ', $search),
370
-              str_replace(', ', '-', $search)
371
-            ))) 
372
-            { 
373
-              $same_found = true;
374
-            }
375
-            $tag_sorted = $this->sort_addtop_if_isnt_in($tag_sorted, $tag);
376
-          }
377
-        }
378
-      }
379
-    }
380
-    
381
-    foreach ($tags as $i => $tag)
382
-    {
383
-      $tag_sorted = $this->sort_addbottom_if_isnt_in($tag_sorted, $tag);
384
-    }
385
-    
386
-    return array(
387
-      'tags'       => $tag_sorted,
388
-      'same_found' => $same_found
389
-    );
390
-  }
391
-  
392 174
   /**
393 175
    *
394 176
    * @param string $string_search 
@@ -408,64 +190,14 @@ class SearchController extends Controller
408 190
         return $this->redirect($this->generateUrl('index'));
409 191
       }
410 192
     }
411
-   
412
-    $string_search = trim($string_search);
413
-    $canonicalizer = new StrictCanonicalizer();
414 193
     
415 194
     if ($this->getRequest()->isXmlHttpRequest())
416 195
     {
417
-      if (strlen($string_search) > 1)
196
+      if (strlen(trim($string_search)) > 1)
418 197
       {
419
-        $words = array_merge(
420
-          explode(' ', $string_search),
421
-          explode('-', $string_search),
422
-          explode(',', $string_search),
423
-          explode(', ', $string_search)
424
-        );
425
-        $where = '';
426
-        $params = array();
427
-        foreach ($words as $i => $word)
428
-        {
429
-          if (strlen($word) > 1)
430
-          {
431
-            $word = $canonicalizer->canonicalize($word);
432
-            if ($where == '')
433
-            {
434
-              $where .= 'WHERE UPPER(t.slug) LIKE :str'.$i;
435
-            }
436
-            else
437
-            {
438
-              $where .= ' OR UPPER(t.slug) LIKE :str'.$i;
439
-            }
440
-
441
-            $params['str'.$i] = '%'.strtoupper($word).'%';
442
-          }
443
-        }
444
-
445
-        $params['uid'] = '%"'.$this->getUserId().'"%';
446
-        $tags = $this->getDoctrine()->getEntityManager()->createQuery("
447
-          SELECT t.name, t.slug, t.id FROM MuzichCoreBundle:Tag t
448
-          $where
449
-          
450
-          AND (t.tomoderate = '0'
451
-          OR t.privateids LIKE :uid)
452
-          
453
-          ORDER BY t.name ASC"
454
-        )->setParameters($params)
455
-        ->getScalarResult()
456
-        ;
457
-        
458
-        $tags_response = array();
459
-        foreach ($tags as $tag)
460
-        {
461
-          $tags_response[] = array(
462
-            'name' => $tag['name'], 
463
-            'id'   => $tag['id'],
464
-            'slug' => $tag['slug']
465
-          );
466
-        }
467
-        
468
-        $sort_response = $this->sort_search_tags($tags_response, $string_search);
198
+        $TagLike = new TagLike($this->getDoctrine());
199
+        $sort_response = $TagLike->getSimilarTags($string_search, $this->getUserId());
200
+      
469 201
         $status = 'success';
470 202
         $error  = '';
471 203
         $message = $this->trans(

+ 348 - 0
src/Muzich/CoreBundle/Util/TagLike.php Voir le fichier

@@ -0,0 +1,348 @@
1
+<?php
2
+
3
+namespace Muzich\CoreBundle\Util;
4
+
5
+use Symfony\Bundle\DoctrineBundle\Registry;
6
+use Muzich\CoreBundle\Util\StrictCanonicalizer;
7
+
8
+/**
9
+ * Cette classe permet d'aider a la recherche de mot similaires a un mot
10
+ * 
11
+ * 
12
+ */
13
+class TagLike
14
+{
15
+  
16
+  protected $registry;
17
+  
18
+  public function __construct(Registry $registry)
19
+  {
20
+    $this->registry = $registry;
21
+  }
22
+  
23
+  /**
24
+   * Ajoute le tag au début du tableau passé en paramètre si celui-ci
25
+   * n'est pas a l'intérieur.
26
+   * 
27
+   * @param array $array
28
+   * @param Tag $tag
29
+   * @return array 
30
+   */
31
+  private function sort_addtop_if_isnt_in($array, $tag)
32
+  {
33
+    $in = false;
34
+    for ($x=0;$x<=sizeof($array)-1;$x++)
35
+    {
36
+      if ($array[$x]['id'] == $tag['id'])
37
+      {
38
+        $in = true;
39
+        break;
40
+      }
41
+    }
42
+    
43
+    if (!$in)
44
+    {
45
+      array_unshift($array, $tag);
46
+      return $array;
47
+    }
48
+    return $array;
49
+  }
50
+  
51
+  /**
52
+   * Ajoute le tag a al fin du tableau passé en paramètre si celui-ci
53
+   * n'est pas a l'intérieur.
54
+   * 
55
+   * @param array $array
56
+   * @param Tag $tag
57
+   * @return array 
58
+   */
59
+  private function sort_addbottom_if_isnt_in($array, $tag)
60
+  {
61
+    $in = false;
62
+    for ($x=0;$x<=sizeof($array)-1;$x++)
63
+    {
64
+      if ($array[$x]['id'] == $tag['id'])
65
+      {
66
+        $in = true;
67
+        break;
68
+      }
69
+    }
70
+    
71
+    if (!$in)
72
+    {
73
+      $array[] = $tag;
74
+      return $array;
75
+    }
76
+    return $array;
77
+  }
78
+  
79
+  /**
80
+   * Construit un tableau de mot a partir du terme de recherche. Ces mots 
81
+   * permettrons la recherche de tags en base et le trie des tags trouvé.
82
+   * 
83
+   * La déduction de ces mot est basé sur:
84
+   *  * Le remplacement des espaces par des tirets et inversement (virgules aussi)
85
+   * 
86
+   * Et chaque mot doit dépasser deux caractères
87
+   * 
88
+   * @param string $search
89
+   * @return array 
90
+   */
91
+  protected function getSearchWordsReplacing($search)
92
+  {
93
+    // En base les tags sont composé de mot a '-' ou a ' '.
94
+    $words = array_unique(array(
95
+      str_replace(' ', '-', $search),
96
+      str_replace('-', ' ', $search),
97
+      str_replace(',', ' ', $search),
98
+      str_replace(', ', ' ', $search),
99
+      str_replace(',', '-', $search),
100
+      str_replace(', ', '-', $search)
101
+    ));
102
+    
103
+    return $words;
104
+  }
105
+  
106
+  /**
107
+   * Construit un tableau de mot a partir du terme de recherche. Ces mots 
108
+   * permettrons la recherche de tags en base et le trie des tags trouvé.
109
+   * 
110
+   * La déduction de ces mot est basé sur:
111
+   *  * La découpe du terme par espaces, tirets et virgules
112
+   * 
113
+   * Et chaque mot doit dépasser deux caractères
114
+   * 
115
+   * @param string $search 
116
+   */
117
+  protected function getSearchWordsExploding($search)
118
+  {
119
+    $words = array_unique(array_merge(
120
+      explode(' ', $search),
121
+      explode('-', $search),
122
+      explode('- ', $search),
123
+      explode(' -', $search),
124
+      explode(' - ', $search),
125
+      explode(',', $search),
126
+      explode(', ', $search),
127
+      explode(' ,', $search),
128
+      explode(' , ', $search)
129
+    ));
130
+    
131
+    $words_filetereds = array();
132
+    foreach ($words as $i => $word)
133
+    {
134
+      if (trim(strlen($word)) > 1)
135
+      {
136
+        $words_filetereds[] = trim($word);
137
+      }
138
+    }
139
+    
140
+    return $words_filetereds;
141
+  }
142
+  
143
+  /**
144
+   * Recherche en base les tags ressemblant a la recherche.
145
+   * 
146
+   * @param array $words
147
+   * @param int $user_id
148
+   * @return array 
149
+   */
150
+  protected function searchTags($words, $user_id)
151
+  {
152
+    $where = '';
153
+    $params = array();
154
+    
155
+    foreach ($words as $i => $word)
156
+    {
157
+      if ($where == '')
158
+      {
159
+        $where .= 'WHERE UPPER(t.slug) LIKE :str'.$i;
160
+      }
161
+      else
162
+      {
163
+        $where .= ' OR UPPER(t.slug) LIKE :str'.$i;
164
+      }
165
+
166
+      $params['str'.$i] = '%'.$word.'%';
167
+    }
168
+
169
+    $params['uid'] = '%"'.$user_id.'"%';
170
+    $tags_query = $this->registry->getEntityManager()->createQuery("
171
+      SELECT t.name, t.slug, t.id FROM MuzichCoreBundle:Tag t
172
+      $where
173
+
174
+      AND (t.tomoderate = '0' OR t.tomoderate IS NULL
175
+      OR t.privateids LIKE :uid)
176
+
177
+      ORDER BY t.name ASC"
178
+    )
179
+      ->setParameters($params)
180
+      ->getScalarResult()
181
+    ;
182
+    
183
+    $tags = array();
184
+    foreach ($tags_query as $tag)
185
+    {
186
+      $tags[] = array(
187
+        'name' => $tag['name'], 
188
+        'id'   => $tag['id'],
189
+        'slug' => $tag['slug']
190
+      );
191
+    }
192
+    
193
+    return $tags;
194
+  }
195
+  
196
+  /**
197
+   * Trie les tags de manière a ce que les tags les plus ressemblant a la 
198
+   * recherche soient au début du tableau.
199
+   * 
200
+   * @param array $tags
201
+   * @param array $search
202
+   * @param array $words
203
+   * @param array $words_replacing
204
+   * @param array $words_exploding
205
+   * @return array 
206
+   */
207
+  protected function sortTags($tags, $search, $words, $words_replacing, $words_exploding)
208
+  {
209
+    $same_found = false;
210
+    $tag_sorted = array();
211
+    
212
+    /*
213
+     * Première passe: Pas plus de trois caractères différents de la recherche
214
+     * On se base ici sur le recherche non découpé
215
+     */
216
+    foreach ($tags as $i => $tag)
217
+    {
218
+      foreach ($words_replacing as $word)
219
+      {
220
+        $word = trim($word);
221
+        if (strlen($word) > 1)
222
+        {
223
+          if (
224
+            strlen(str_replace($word, '', strtoupper($tag['slug']))) < 4
225
+            // Si on tombe sur le même nom, il ne faut pas le mettre en haut maintenant
226
+            // ou il se fera décallé vers le bas
227
+            && $word != $search
228
+          )
229
+          {
230
+            $tag_sorted = $this->sort_addtop_if_isnt_in($tag_sorted, $tag);
231
+          }
232
+        }
233
+      }
234
+      
235
+    }
236
+    
237
+    /*
238
+     * Pour une recherche en plusieurs mots, on cherche la similitude avec 
239
+     * des tags. De façon a ce que "dark psytrance" soit reconnue comme proche de
240
+     * "psytrance dark".
241
+     */
242
+    if (count($words_exploding) > 1)
243
+    {
244
+    
245
+      $tags_counteds = array();
246
+      /*
247
+       * Pour chaque mot qui compose la recherche on regarde si ce mot
248
+       * est compris dans un des tags
249
+       */
250
+      foreach ($tags as $i => $tag)
251
+      {
252
+        foreach ($words_exploding as $word)
253
+        {
254
+          if (strpos(strtoupper($tag['slug']), $word) !== false)
255
+          {
256
+            $count = 1;
257
+            if (array_key_exists($tag['id'], $tags_counteds))
258
+            {
259
+              $count = ($tags_counteds[$tag['id']]['count'])+1;
260
+            }
261
+            $tags_counteds[$tag['id']] = array(
262
+              'count' => $count,
263
+              'tag'   => $tag
264
+            );
265
+          }
266
+        }
267
+      }
268
+
269
+      /*
270
+       * Maintenant que l'on a un tableau contenant les tags comportant au moins
271
+       * un mot identique a recherche
272
+       */
273
+      foreach ($tags_counteds as $id => $counted)
274
+      {
275
+        // Si le tag a eu plus d'une fois le même mot
276
+        if ($counted['count'] > 1)
277
+        {
278
+          // Ci-dessous on va chercher a voir si le tag et la recherche on le 
279
+          // même nombre de mots, si c'est le cas on pourra considérer cette 
280
+          // recherche comme lié a un tag connu.
281
+
282
+          if (count($words_exploding) == count($this->getSearchWordsExploding($counted['tag']['slug'])))
283
+          {
284
+            $same_found = true;
285
+          }
286
+
287
+          // Cette verif permet de ne pas ajouter les tags qui n'ont qu'un mot
288
+          // Si on ajouté ce tag maintenant il ne serais pas ajouté au controle en dessous
289
+          // (nom identique) et donc pas au dessus.
290
+          $tag_sorted = $this->sort_addtop_if_isnt_in($tag_sorted, $counted['tag']);
291
+
292
+        }
293
+      }
294
+    
295
+    }
296
+    
297
+    /*
298
+     * On recherche maintenant les noms de tags identique a la recherche
299
+     */
300
+    foreach ($tags as $i => $tag)
301
+    {      
302
+      foreach ($words_replacing as $word)
303
+      {
304
+        if ($word == strtoupper($tag['slug']))
305
+        {
306
+          // Ci-dessous on déduit si le mot est identique au tag . 
307
+          // De façon a ce que si c'est le cas, pouvoir dire:
308
+          // oui le terme recherché est connu.
309
+          if (in_array($word, $words_replacing))
310
+          { 
311
+            $same_found = true;
312
+          }
313
+          $tag_sorted = $this->sort_addtop_if_isnt_in($tag_sorted, $tag);
314
+        }
315
+      }
316
+    }
317
+    
318
+    foreach ($tags as $i => $tag)
319
+    {
320
+      $tag_sorted = $this->sort_addbottom_if_isnt_in($tag_sorted, $tag);
321
+    }
322
+    
323
+    return array(
324
+      'tags'       => $tag_sorted,
325
+      'same_found' => $same_found
326
+    );
327
+  }
328
+  
329
+  public function getSimilarTags($search, $user_id)
330
+  {
331
+    $canonicalizer = new StrictCanonicalizer();
332
+    $search = strtoupper($canonicalizer->canonicalize(trim($search)));
333
+    
334
+    if (strlen($search) < 2)
335
+    {
336
+      // Le terme de recherche doit faire au moins 2 caractéres
337
+      return array('tags' => array(), 'same_found' => false);
338
+    }
339
+    
340
+    $words_e = $this->getSearchWordsExploding($search);
341
+    $words_r = $this->getSearchWordsReplacing($search);
342
+    $words   = array_unique(array_merge($words_e, $words_r));
343
+    $tags    = $this->searchTags($words, $user_id);
344
+    
345
+    return $this->sortTags($tags, $search, $words, $words_r, $words_e);
346
+  }
347
+  
348
+}