src/V4Bundle/Controller/FoodController.php line 163

Open in your IDE?
  1. <?php
  2. namespace App\V4Bundle\Controller;
  3. use App\Entity\Billing;
  4. use App\Entity\Command;
  5. use App\Entity\Food;
  6. use App\Entity\ServiceCategories;
  7. use App\V4Bundle\Entity\Billing2;
  8. use Doctrine\ORM\EntityManagerInterface;
  9. use Symfony\Component\HttpFoundation\JsonResponse;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\Routing\Annotation\Route;
  12. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  13. use Symfony\Component\Serializer\SerializerInterface;
  14. use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
  15. use GuzzleHttp\Client;
  16. class FoodController extends BaseController
  17. {
  18.     /**
  19.      * Groupes de recherche flexibles
  20.      */
  21.     private const SEARCH_GROUPS = [
  22.         'spagho' => [
  23.             'include' => [       
  24.             'spaghett',
  25.             'spaghetti',
  26.             'pâtes',
  27.             'pates',
  28.             'pasta',],
  29.             'exclude' => [],
  30.             'name_only' => true,
  31.         ],
  32.        'poissonbraise' => [
  33.         'include' => ['poisson''brais'],
  34.         'exclude' => ['soupe''poulet''pintade''porc'],
  35.         'mode' => 'all'
  36.         ],
  37.         'foufou' => [
  38.             'include' => ['foufou''fufu'],
  39.             'exclude' => [],
  40.         ],
  41.     
  42.         'akoumé' => [
  43.             'include' => ['akoum'],
  44.             'exclude' => [],
  45.         ],
  46.     
  47.         'émakoume' => [
  48.             'include' => ['makoum''emakoum'],
  49.             'exclude' => [],
  50.         ],
  51.     
  52.         'degue' => [
  53.             'include' => ['degue''dèguè'],
  54.             'exclude' => [],
  55.         ],
  56.     
  57.         'botokoin' => [
  58.             'include' => ['botokoin'],
  59.             'exclude' => [],
  60.         ],
  61.     
  62.         'tchintchinga' => [
  63.             'include' => ['tchintchinga'],
  64.             'exclude' => [],
  65.         ],
  66.     
  67.         'brochettes' => [
  68.             'include' => ['brochette','brochettes'],
  69.             'exclude' => [],
  70.         ],
  71.     
  72.         'pizza' => [
  73.             'include' => ['pizza'],
  74.             'exclude' => [],
  75.         ],
  76.     
  77.         'burger' => [
  78.             'include' => ['burger'],
  79.             'exclude' => [],
  80.         ],
  81.     
  82.         'shawarma' => [
  83.             'include' => ['shaw''charw''chaw'],
  84.             'exclude' => [],
  85.         ],
  86.         'poulet' => [
  87.             'include' => ['poulet','nugget','poule','chicken','wings'],
  88.             'exclude' => [],
  89.         ],
  90.         'riz' => [
  91.             'include' => ['riz','ayimolou','watchi','wachi'],
  92.             'exclude' => [],
  93.         ],
  94.         'jus' => [
  95.             'include' => ['jus'],
  96.             'exclude' => [],
  97.             'name_only' => true,
  98.         ],
  99.         'smoothie' => [
  100.             'include' => ['smooth'],
  101.             'exclude' => [],
  102.             'name_only' => true,
  103.         ],
  104.         'milkshakes' => [
  105.             'include' => ['milk''shake'],
  106.             'exclude' => [],
  107.             'name_only' => true,
  108.         ],
  109.         'théaulait' => [
  110.             'include' => ['thé au lait''théaulait'],
  111.             'exclude' => [],
  112.             'name_only' => true,
  113.         ],
  114.         'glaces' => [
  115.             'include' => [
  116.                 'glace',
  117.                 'ice cream',
  118.                 'ice-cream',
  119.                 'coupe',
  120.                 'sorbet',
  121.                 'sundae',
  122.                 'cône',
  123.                 'Eskimo'
  124.             ],
  125.             'exclude' => [
  126.                 'burger','pizza','shawarma','chawarma','crêpe','spaghett','pasta',
  127.                 'poulet','chicken','riz','jus','smoothie','café','cafe','latte','thé','théaulait','milkshakes',
  128.                 'milkshake','bouillie','attieke','garba','champagne','rice','poisson''soupe','beignet','epice','epices'
  129.             ],
  130.             'name_only' => true,
  131.         ],
  132.         'crêpes' => [
  133.             'include' => ['crep''crêp'],
  134.             'exclude' => [],
  135.         ],
  136.     
  137.         'bouillie' => [
  138.             'include' => ['bouillie'],
  139.             'exclude' => [],
  140.             'name_only' => true,
  141.             'match' => 'starts_with'
  142.         ],
  143.         'attieke' => [
  144.             'include' => ['attieke','garba','atchiéké','Atsèké'],
  145.             'exclude' => [],
  146.         ],
  147.     ];
  148. /**
  149.  * @Route("mobile/api/v4/foods/search", name="user_mobile_api_search_food", methods={"GET"})
  150.  */
  151. public function searchTopFoods(Request $request): JsonResponse
  152. {
  153.     $filter   strtolower(trim((string) $request->query->get('filter')));
  154.     $category strtolower(trim((string) $request->query->get('category')));
  155.     $em $this->entityManager;
  156.     /**
  157.      * -----------------------------------------
  158.      * 1️⃣ BASE QUERY : FOOD → RESTAURANT → CATEGORY
  159.      * -----------------------------------------
  160.      */
  161.     $qb $em->getRepository(Food::class)->createQueryBuilder('f')
  162.         ->innerJoin('f.restaurant''r')
  163.         ->innerJoin(ServiceCategories::class, 's''WITH''r.category = s.id')
  164.         ->andWhere('f.deleted = 0')
  165.         ->andWhere('f.state = 1')
  166.         ->andWhere('r.enabled = 1')
  167.         ->andWhere('r.isDisabled = 0');
  168.     /**
  169.      * -----------------------------------------
  170.      * 2️⃣ FILTER BY RESTAURANT CATEGORY (CONTEXT)
  171.      * -----------------------------------------
  172.      */
  173.     $service_category null;
  174.     if ($category && $category != 'all') {
  175.         $service_category $em
  176.             ->getRepository(ServiceCategories::class)
  177.             ->findOneBy(['key' => $category]);
  178.         if ($service_category) {
  179.             $qb
  180.                 ->andWhere('s.id = :serviceCategoryId')
  181.                 ->setParameter('serviceCategoryId'$service_category->getId());
  182.         }
  183.     }
  184.     /**
  185.  * -----------------------------------------
  186.  * 3️⃣ TEXT SEARCH (WITH GROUPS)
  187.  * -----------------------------------------
  188.  */
  189. if (!empty($filter)) {
  190.     $filter strtolower($filter);
  191.     $terms  preg_split('/\s+/'$filter);
  192.     /**
  193.      * -----------------------------------------
  194.      * CASE A — FILTER GROUP EXISTS
  195.      * -----------------------------------------
  196.      */
  197.     if (isset(self::SEARCH_GROUPS[$filter])) {
  198.         $group self::SEARCH_GROUPS[$filter];
  199.         $mode  $group['mode'] ?? 'any'// any | all
  200.         $nameOnly $group['name_only'] ?? false;
  201.         // INCLUDE
  202.         if ($mode === 'any') {
  203.             $orX $qb->expr()->orX();
  204.             foreach ($group['include'] as $i => $term) {
  205.                 $match $group['match'] ?? 'contains';
  206.                 $pattern = ($match === 'starts_with')
  207.                     ? $term '%'
  208.                     '%' $term '%';
  209.                 if ($nameOnly) {
  210.                     $orX->add(
  211.                         $qb->expr()->like('LOWER(f.name)'":inc$i")
  212.                     );
  213.                 } else {
  214.                     $orX->add(
  215.                         $qb->expr()->orX(
  216.                             $qb->expr()->like('LOWER(f.name)'":inc$i"),
  217.                             $qb->expr()->like('LOWER(f.description)'":inc$i")
  218.                         )
  219.                     );
  220.                 }
  221.                 $qb->setParameter("inc$i"$pattern);
  222.             }
  223.             $qb->andWhere($orX);
  224.         } else {
  225.         
  226.             foreach ($group['include'] as $i => $term) {
  227.                 if ($nameOnly) {
  228.                     $qb
  229.                         ->andWhere(
  230.                             $qb->expr()->like('LOWER(f.name)'":inc$i")
  231.                         );
  232.                 } else {
  233.                     $qb
  234.                         ->andWhere(
  235.                             $qb->expr()->orX(
  236.                                 $qb->expr()->like('LOWER(f.name)'":inc$i"),
  237.                                 $qb->expr()->like('LOWER(f.description)'":inc$i")
  238.                             )
  239.                         );
  240.                 }
  241.                 $qb->setParameter("inc$i""%$term%");
  242.             }
  243.         }
  244.         foreach ($group['exclude'] as $i => $term) {
  245.            $qb->andWhere(
  246.             $qb->expr()->andX(
  247.                 'LOWER(f.name) NOT LIKE :exc'.$i,
  248.                 'LOWER(f.description) NOT LIKE :exc'.$i
  249.             )
  250.         );
  251.         $qb->setParameter('exc'.$i"%$term%");
  252.         }
  253.     } else {
  254.         $terms array_values(array_filter($terms, function ($t) {
  255.             return strlen($t) >= 2;
  256.         }));
  257.         $termCount count($terms);
  258.         // single word → large
  259.         if ($termCount === 1) {
  260.             $term $terms[0];
  261.             $qb
  262.                 ->andWhere(
  263.                     $qb->expr()->orX(
  264.                         $qb->expr()->like('LOWER(f.name)'':term'),
  265.                         $qb->expr()->like('LOWER(f.description)'':term')
  266.                     )
  267.                 )
  268.                 ->setParameter('term'"%$term%");
  269.         }
  270.         // multiple words → AND
  271.         if ($termCount >= 2) {
  272.             foreach ($terms as $i => $term) {
  273.                 $qb
  274.                     ->andWhere(
  275.                         $qb->expr()->orX(
  276.                             $qb->expr()->like('LOWER(f.name)'":t$i"),
  277.                             $qb->expr()->like('LOWER(f.description)'":t$i")
  278.                         )
  279.                     )
  280.                         ->setParameter("t$i""%$term%");
  281.                 }
  282.             }
  283.         }
  284.     }
  285.     /**
  286.      * -----------------------------------------
  287.      * 4️⃣ FETCH FOODS
  288.      * -----------------------------------------
  289.      */
  290.     $foods $qb
  291.         ->setMaxResults(1000)
  292.         ->getQuery()
  293.         ->getResult();
  294.     if (!$foods) {
  295.         return $this->json([
  296.             'success' => true,
  297.             'count'   => 0,
  298.             'data'    => [],
  299.         ]);
  300.     }
  301.     /**
  302.      * -----------------------------------------
  303.      * 5️⃣ STATS (MOST ORDERED)
  304.      * -----------------------------------------
  305.      */
  306.     $foodStats = [];
  307.     $foodsByRestaurant = [];
  308.     foreach ($foods as $food) {
  309.         $foodId $food->getId();
  310.         $restaurantId $food->getRestaurantId();
  311.         $foodStats[$foodId] = [
  312.             'food'  => $food,
  313.             'count' => 0,
  314.         ];
  315.         $foodsByRestaurant[$restaurantId][] = $foodId;
  316.     }
  317.     foreach ($foodsByRestaurant as $restaurantId => $foodIds) {
  318.         $commands $em->createQuery(
  319.             'SELECT c.foodCommand
  320.              FROM App:Command c
  321.              WHERE c.restaurantId = :resto
  322.              AND c.state = 3'
  323.         )
  324.         ->setParameter('resto'$restaurantId)
  325.         ->getScalarResult();
  326.         if (!$commands) {
  327.             continue;
  328.         }
  329.         $foodIdLookup array_flip($foodIds);
  330.         foreach ($commands as $row) {
  331.             $items = @unserialize($row['foodCommand']);
  332.             if ($items === false) {
  333.                 $items json_decode($row['foodCommand'], true);
  334.             }
  335.             if (!is_array($items)) {
  336.                 continue;
  337.             }
  338.             foreach ($items as $item) {
  339.                 if (!isset($item['food_id'])) {
  340.                     continue;
  341.                 }
  342.                 $fid = (int) $item['food_id'];
  343.                 if (!isset($foodIdLookup[$fid])) {
  344.                     continue;
  345.                 }
  346.                 $qty = (int) ($item['quantity'] ?? 1);
  347.                 $foodStats[$fid]['count'] += $qty;
  348.             }
  349.         }
  350.     }
  351.     uasort($foodStats, function ($a$b) {
  352.         return $b['count'] <=> $a['count'];
  353.     });
  354.     /**
  355.      * -----------------------------------------
  356.      * 6️⃣ BILLING (AS-IS)
  357.      * -----------------------------------------
  358.      */
  359.     $billing_array = [
  360.         'phoneNumber' => [],
  361.         'email'       => [],
  362.     ];
  363.     foreach ($em->getRepository(Billing::class)->findAll() as $item) {
  364.         $billing_array['phoneNumber'][] = $this->billingToArrayLite($item);
  365.     }
  366.     foreach ($em->getRepository(Billing2::class)->findAll() as $item) {
  367.         $billing_array['email'][] = $this->billingToArrayLite($item);
  368.     }
  369.     if ($billing_array['phoneNumber']) {
  370.         usort($billing_array['phoneNumber'], [$this'sort_billing_by_startRange']);
  371.     }
  372.     if ($billing_array['email']) {
  373.         usort($billing_array['email'], [$this'sort_billing_by_startRange']);
  374.     }
  375.     /**
  376.      * -----------------------------------------
  377.      * 7️⃣ RESPONSE
  378.      * -----------------------------------------
  379.      */
  380.     $data = [];
  381.     foreach ($foodStats as $item) {
  382.         $data[] = $this->foodApiToArray($item['food']) + [
  383.             'ordered_count' => $item['count'],
  384.         ];
  385.     }
  386.     return $this->json([
  387.         'service_category' => $service_category $service_category->getId() : null,
  388.         'data'             => $data,
  389.         'billing'          => $billing_array,
  390.         'error'            => 0,
  391.     ], 200);
  392. }
  393. }