Symfony : contrainte d'unicité qui fonctionne mal

a marqué ce sujet comme résolu.

Bonjour (et bonne année !)

J’en suis à tester mon application de Salon du Livre. J’essaie de faire un script qui me remplirait ma BDD avec de fausses factures afin de travailler sur les pages de présentation des résultats. C’est évidemment un script bien lourd mais je compte ne l’utiliser qu’une seule fois (800+ requêtes)…

Mon souci se passe avec l’entité Stock qui comptabilise les livres vendus. Vu que je pars d’une table vide, lorsque je crée mes Factures je crée un Stock pour chaque livre. Puis, si le Stock existe déjà, on le met à jour. Et c’est là où je dois me planter ou mal comprendre le fonctionnement de Doctrine. Doctrine lève une erreur de non respect de la contrainte d’unicité et je vois mal pourquoi mon script m’envoie dans cette direction alors que je pensais justement avoir écrit quelque chose qui évite justement cela.

Pourrais-je avoir vos avis s’il vous plait ? Merci

//Stock
$stock = $stockRep->findOneBy(['Book' => $book]);

if (!$stock) {
    $stock = new Stock();   
    $stock->setBook($book);
    $stock->setAuthor($author);
    $stock->setOrigin('library');                                   
} 
                                 
$sold = $stock->getSold();
$stock->setSold($sold + $checkoutLine->getQuantity());
$em->persist($stock);    

C’est un morceau du script un peu plus grand que voici, pour info :

#[Route('/caisse/generer-des-factures', name: 'checkout.fake')]
    public function generate(BookRepository $bookRep, StockRepository $stockRep, AuthorRepository $authorRep, EntityManagerInterface $em): void
    {       
        $books = $bookRep->findAll();
        $faker = Factory::create('fr_FR');

        $cp = ['35000', '44000', '33000', '75000', '59000', '13000'];
        $paymentMethods = ['Chèque', 'Carte bancaire', 'Espèces', 'Mairie', 'Autre'];
        $quantities = ['1', '1', '1', '1', '2'];

        for ($i = 0; $i < 150; $i++) {
            $checkout = new Checkout();

            $checkout->setCustomerName($faker->lastName());
            $checkout->setCustomerPostalCode($cp[array_rand($cp)]);
            $checkout->setPaymentMethod($paymentMethods[array_rand($paymentMethods)]);

            $totalAmount = 0;
            $j = rand(1, 4);
            for ($k = 1; $k <= $j; $k++) {
                $checkoutLine = new CheckoutLine();   
                
                $book = $books[array_rand($books)]; 
                
                $authors = $book->getAuthor();
                $author = $authors->first(); //un livre peut avoir plusieurs auteurs. Pour ce test je n'en ai besoin que d'un
                
                $checkoutLine->setBook($book);
                $checkoutLine->setQuantity($quantities[array_rand($quantities)]);
                $checkoutLine->setLineAmount($book->getPrice() * $checkoutLine->getQuantity());
                $totalAmount = $totalAmount + $checkoutLine->getLineAmount();
                $checkoutLine->setCheckout($checkout);
                $em->persist($checkoutLine);

                //Stock
                $stock = $stockRep->findOneBy(['Book' => $book]);

                if (!$stock) {
                    $stock = new Stock();   
                    $stock->setBook($book);
                    $stock->setAuthor($author);
                    $stock->setOrigin('library');                                   
                } 
                                 
                $sold = $stock->getSold();
                $stock->setSold($sold + $checkoutLine->getQuantity());
                $em->persist($stock);                               
            }

            $checkout->setTotalAmount($totalAmount);
            $checkout->setCreatedAt(\DateTimeImmutable::createFromMutable($faker->dateTimeBetween('2025-12-06 09:00:00', '2025-12-06 19:00:00')));
            
            $em->persist($checkout);
        }        

        $em->flush();
    }

Pour info, si je n’ai pas utilisé Fixtures, c’est que ce système purge intégralement ma BDD, hors je ne veux pas toucher à la table des livres, qui contient de vraies données liées aux vrais auteurs, qui n’ont pas à être effacées.

Et encore pour info, ce script fonctionne si je vire la partie sur les Stocks, mais bon, ne pas comptabiliser les chiffres de vente, c’est un peu foireux. Ceci dit, je peux sans doute les créer a posteriori, mais ça me semble un peu complexe pour quelque chose qui ne le devrait pas.

Salut ! Bonne année à toi aussi

Tu effectues un seul flush() en fin de script, ce qui fait que toutes les opérations sur la base de données sont effectuées une seule fois à ce moment. Du coup, comme rien n’existe en base avant, rien n’y est retrouvé quand tu y cherches quelque chose…

Je te propose d’enregistrer les stocks dans un tableau indexé par ID de livre, et de faire avec cette liste.

+0 -0

UnitOfWork ne gère pas les contraintes d’unicité, mais uniquement ce qui sera ajouté/mis à jour/enlevé de la base de données. Les contraintes sont gérées par le composant de validation de Symfony, et je ne pense pas que lancer explicitement la validation ne te permettrait que de savoir s’il y a doublon (voire plus), mais pas facilement de récupérer ceux-ci et encore moins celui à conserver.

Je ne suis pas sûr que les requêtes pour vérifier l’unicité sont visibles dans le panneau de débogage ad hoc, mais si c’est le cas, tu pourrais les y voir avant celles d’insertion. Et de toute manière, findOneByBook(…) va lancer une requête (tu peux en suivre l’exécution par , ici et . Donc, à moins d’avoir déjà des données en base avant la génération, $stockRep->findOneBy(…) ne retournera jamais rien tant que flush() n’aura pas été appelé.

D’où mon conseil réitéré de faire un tableau indexé par ID de livre. Je modifierais ainsi ton premier bloc de code fourni comme suit.

//Stock
if (!isset($stocks[$book->getId()]) {
    $stock = new Stock();
    $stock->setBook($book);
    $stock->setAuthor($author);
    $stock->setOrigin('library');
    $em->persist($stock); // Oui, déplacé ici
    $stocks[$book->getId() = &$stock; // pas certain que le & soit nécessaire
}
$stock = &$stocks[$book->getId()]; // idem, pas certain que le & soit nécessaire

$sold = $stock->getSold();
$stock->setSold($sold + $checkoutLine->getQuantity());
// Non, pas besoin de re-persister le stock à chaque modification
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte