src/Controller/Admin/Player/VouchersController.php line 79

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Admin\Player;
  3. use App\Controller\Admin\AbstractVoucherController;
  4. use App\Entity\Player;
  5. use App\Entity\VoucherGroup;
  6. use App\Form\Admin\VoucherCreateType;
  7. use App\Form\Admin\VoucherImportType;
  8. use App\Repository\ClientRepository;
  9. use App\Repository\PlayerRepository;
  10. use App\Repository\VoucherRepository;
  11. use App\Service\VoucherPreloader;
  12. use App\Validation\Admin\VoucherCreateContract;
  13. use App\Validation\Admin\VoucherImportContract;
  14. use DateTime;
  15. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  16. use Doctrine\ORM\EntityManagerInterface;
  17. use DomainException;
  18. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  19. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  20. use Symfony\Component\Form\Form;
  21. use Symfony\Component\Form\FormError;
  22. use Symfony\Component\HttpFoundation\File\File;
  23. use Symfony\Component\HttpFoundation\Request;
  24. use Symfony\Component\HttpFoundation\RequestStack;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. use Symfony\Component\Security\Core\Security;
  27. use Symfony\Component\Validator\Validator\ValidatorInterface;
  28. use Twig\Environment;
  29. /**
  30.  * @Route("/players/{slug}/vouchers")
  31.  * @IsGranted("ROLE_CLIENT")
  32.  * @Cache(mustRevalidate=true)
  33.  */
  34. class VouchersController extends AbstractVoucherController
  35. {
  36.   private ValidatorInterface $validator;
  37.   private VoucherRepository $voucherRepo;
  38.   private VoucherPreloader $voucherPreloader;
  39.   
  40.   public function __construct(ClientRepository $clientrepoEntityManagerInterface $emRequestStack $stackEnvironment $twigSecurity $securityValidatorInterface $validatorVoucherRepository $voucherRepoVoucherPreloader $voucherPreloader) {
  41.     parent::__construct($clientrepo$em$stack$twig$security);
  42.     $this->validator $validator;
  43.     $this->voucherRepo $voucherRepo;
  44.     $this->voucherPreloader $voucherPreloader;
  45.   }
  46.   /**
  47.    * @Route("/")
  48.    */
  49.   public function list(Request $r$slugPlayerRepository $playerrepoVoucherPreloader $voucherPreloader) {
  50.     $player $playerrepo->getBySlug($slug);
  51.     if(!$player) {
  52.       throw $this->createNotFoundException();
  53.     }
  54.     
  55.     $client $this->ensureClient($player->getClient());
  56.     $this->setPlayerContext($player);
  57.     // Handle creation form
  58.     $createForm $this->createForm(VoucherCreateType::class, new VoucherCreateContract(), [
  59.       'client' => $client,
  60.       'player' => $player,
  61.     ]);
  62.     $createForm->handleRequest($r);
  63.     $importForm $this->createForm(VoucherImportType::class);
  64.     $importForm->handleRequest($r);
  65.     if($createForm->isSubmitted() && $createForm->isValid()) {
  66.       $this->voucherRepo->createVouchers($player$createForm->getData(), $this->getUser());
  67.       $this->addFlash('success'"Voucher(s) created");
  68.       return $this->redirectToRoute("app_admin_player_vouchers_list", ['slug' => $slug]);
  69.     }
  70.     if($importForm->isSubmitted() && $importForm->isValid()) {
  71.       if($this->handleImport($player$importForm)) {
  72.         return $this->redirectToRoute("app_admin_player_vouchers_list", ['slug' => $slug]);
  73.       }
  74.     }
  75.     return $this->renderVoucherList('admin/player/vouchers.html.twig'$createForm$importForm$voucherPreloader->areVoucherPreloaded($player));
  76.   }
  77.   
  78.   protected function handleImport(Player $playerForm $importForm): bool {
  79.     $file $importForm->get("file")->getData();
  80.     
  81.     try {
  82.         $codes $this->getCodesFromFile($player$file);
  83.         $total count($codes);
  84.         
  85.         error_log("File contains {$total} codes");
  86.         $batches array_chunk($codesVoucherRepository::MAX_VOUCHER_IMPORT);
  87.         
  88.         $created 0;
  89.         foreach($batches as $batch_codes) {
  90.           error_log("import memory usage: "round(memory_get_peak_usage()/1024/1024,2)." MB");
  91.           $created += $this->voucherRepo->importVouchers($player$this->getUser(), $batch_codes);
  92.           error_log(count($batch_codes)." codes imported");
  93.         }
  94.         
  95.         if($this->voucherPreloader->areVoucherPreloaded($player)) {
  96.           $vouchers array_values(array_map(fn(VoucherImportContract $vc) => $vc->code$codes));
  97.           $this->voucherPreloader->preloadVouchersForPlayer($player$vouchers);
  98.         }
  99.         
  100.         $ignored $total $created;
  101.         if($created 0) {
  102.           $this->addFlash('success'"{$created} voucher(s) imported");
  103.         }
  104.         if($ignored 0) {
  105.           $this->addFlash('warning'"{$ignored} duplicated voucher(s) ignored");
  106.         }
  107.        
  108.         return true;
  109.       } catch(DomainException $ex) {
  110.         $importForm->get('file')->addError(new FormError($ex->getMessage()));
  111.       } catch(UniqueConstraintViolationException $ex) {
  112.         preg_match("/Duplicate entry '[0-9]+-([^']+)'/"$ex->getMessage(), $matches);
  113.         $duplicate $matches[1] ?? 'unkwnown';
  114.         $importForm->get('file')->addError(new FormError("Can't import file, one or more codes already exists ({$duplicate})"));
  115.       }
  116.       
  117.       return false;
  118.   }
  119.   protected function getCodesFromFile(Player $playerFile $file): array {
  120.     $fp fopen($file->getPathname(), 'r');
  121.     $columns fgetcsv($fp128';');
  122.     if(!is_array($columns) || !count($columns)) {
  123.       throw new DomainException("Aucune colonne dans le fichier");
  124.     }
  125.     $allColumns = ['type''code''group'];
  126.     $requiredColumns = ['type''code'];
  127.     if(!$player->getIsLive()) {
  128.       $allColumns array_merge($allColumns, ['replay''validitystart''validityend']);
  129.       $requiredColumns[] = "replay";
  130.     }
  131.     
  132.     // Compute mappings
  133.     $mapping = [];
  134.     foreach($columns as $i => $col) {
  135.       $mapped false;
  136.       foreach($allColumns as $name) {
  137.         if(strtolower(trim($col)) == $name) {
  138.           $mapping[$name] = $i;
  139.           $mapped true;
  140.           break;
  141.         }
  142.       }
  143.       if(!$mapped) {
  144.         throw new \DomainException("Column '{$col}' is not supported");
  145.       }
  146.     }
  147.     // Check required columns
  148.     foreach($requiredColumns as $name) {
  149.       if(!isset($mapping[$name])) {
  150.         throw new DomainException("Missing {$name} column");
  151.       }
  152.     }
  153.     
  154.     // Get list of voucher groups
  155.     // Todo: don't load all voucher groups, get on need basis while discovering codes
  156.     $groups array_reduce($player->getClient()->getVoucherGroups()->toArray(), fn($resultVoucherGroup $group) => $result + [$group->getName() => $group], []);
  157.     $defaultGroup $player->getClient()->getDefaultGroup();
  158.     $line 1;
  159.     $codes = [];
  160.     while(!feof($fp)) {
  161.       $data fgetcsv($fp128';');
  162.       // Ignore empty lines
  163.       if(!$data || !count($data)) {
  164.         continue;
  165.       }
  166.       if(count($data) != count($mapping)) {
  167.         throw new DomainException("Bad number of columns in line en ligne $line");
  168.       }
  169.       $code = new VoucherImportContract();
  170.       $code->type trim($data[$mapping['type']]);
  171.       $code->code trim($data[$mapping['code']]);
  172.       if(!isset($mapping['group'])) {
  173.         $code->group $defaultGroup;
  174.       } else {
  175.         $code->group $groups[trim($data[$mapping['group']])] ?? null;
  176.         if(!$code->group) {
  177.           throw new DomainException("Inexistant voucher group {$data[$mapping['group']]} in line {$line}");
  178.         }
  179.       }
  180.       if(isset($mapping['validitystart'])) {
  181.         $code->dateStart DateTime::createFromFormat("Y-m-d"trim($data[$mapping['validitystart']]));
  182.       }
  183.       if(isset($mapping['validityend'])) {
  184.         $code->dateEnd DateTime::createFromFormat("Y-m-d"trim($data[$mapping['validityend']]));
  185.       }
  186.       
  187.       $errors $this->validator->validate($code);
  188.       if(count($errors)) {
  189.         throw new \DomainException("Error line {$line} for code {$code->code}{$errors[0]->getMessage()}");
  190.       }
  191.       $codes[$code->code] = $code;
  192.       $line++;
  193.     }
  194.     
  195.     fclose($fp);
  196.     unlink($file->getPathname());
  197.     
  198.     return $codes;
  199.   }
  200. }