Partage
  • Partager sur Facebook
  • Partager sur Twitter

Utiliser un champ manytoone dans un querybuilder

Sujet résolu
    3 février 2023 à 19:58:51

    Bonjour,

    Je souhaite faire une requête avec la dernière version de symfony et de doctrine (symfony 6.2.4 et doctrine/orm 2.14). Je pensais cela simple mais finalement assez complexe et je m'en sors pas.

    Dans mon application j'ai une table mouvements financiers ou je liste tous les mouvements financiers pour la gestion d'un site web. Chaque dépense ou gain d'argent est classée dans cette table. Pour toutes ces dépenses ou ces gains d'argents je leur assigne des catégories. Par exemple

    • 100 euros => catégorie : hébergement
    • 30 euros => catégorie : hébergement
    • 50 euros => catégorie : licence logiciel
    • etc
    Ces dépenses sont enregistrées dans une table mouvements_finances. Dans cette table j'ajoute l'identifiant de la catégorie (dépenses ou crédits). Une relation ManyToOne entre la table mouvements_finances et la table categories_credits et categories_depenses existe.
    Dans symfony j'ai créé les entités suivantes
    MouvementsFinances
    <?php
    
    namespace App\Entity;
    
    use App\Repository\MouvementsFinancesRepository;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\Common\Collections\Collection;
    use Doctrine\DBAL\Types\Types;
    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity(repositoryClass: MouvementsFinancesRepository::class)]
    class MouvementsFinances
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(nullable: true)]
        private ?float $montant = null;
    
        #[ORM\Column(length: 255, nullable: true)]
        private ?string $typedepenses = null;
    
        #[ORM\Column(type: Types::TEXT, nullable: true)]
        private ?string $remarque = null;
    
        #[ORM\ManyToOne(inversedBy: 'mouvementsFinances')]
        private ?Website $mouvementwebsite = null;
    
        #[ORM\ManyToOne]
        private ?CategoriesCredits $catcredit = null;
    
        #[ORM\ManyToOne]
        private ?CategoriesDepenses $catdebit = null;
    
        #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
        private ?\DateTimeInterface $date = null;
    
        public function getId(): ?int
        {
            return $this->id;
        }
    
        public function getMontant(): ?float
        {
            return $this->montant;
        }
    
        public function setMontant(?float $montant): self
        {
            $this->montant = $montant;
    
            return $this;
        }
    
        public function getTypedepenses(): ?string
        {
            return $this->typedepenses;
        }
    
        public function setTypedepenses(?string $typedepenses): self
        {
            $this->typedepenses = $typedepenses;
    
            return $this;
        }
    
        public function getRemarque(): ?string
        {
            return $this->remarque;
        }
    
        public function setRemarque(?string $remarque): self
        {
            $this->remarque = $remarque;
    
            return $this;
        }
    
        public function getMouvementwebsite(): ?Website
        {
            return $this->mouvementwebsite;
        }
    
        public function setMouvementwebsite(?Website $mouvementwebsite): self
        {
            $this->mouvementwebsite = $mouvementwebsite;
    
            return $this;
        }
    
        public function getCatcredit(): ?CategoriesCredits
        {
            return $this->catcredit;
        }
    
        public function setCatcredit(?CategoriesCredits $catcredit): self
        {
            $this->catcredit = $catcredit;
    
            return $this;
        }
    
        public function getCatdebit(): ?CategoriesDepenses
        {
            return $this->catdebit;
        }
    
        public function setCatdebit(?CategoriesDepenses $catdebit): self
        {
            $this->catdebit = $catdebit;
    
            return $this;
        }
    
        public function getDate(): ?\DateTimeInterface
        {
            return $this->date;
        }
    
        public function setDate(?\DateTimeInterface $date): self
        {
            $this->date = $date;
    
            return $this;
        }
    }
    
     et mon entité CategoriesDepenses
    <?php
    
    namespace App\Entity;
    
    use App\Repository\CategoriesDepensesRepository;
    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity(repositoryClass: CategoriesDepensesRepository::class)]
    class CategoriesDepenses
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(length: 255)]
        private ?string $nom = null;
      
      public function __toString(): string {
        return (string) $this->getNom();
      }
    
        public function getId(): ?int
        {
            return $this->id;
        }
    
        public function getNom(): ?string
        {
            return $this->nom;
        }
    
        public function setNom(string $nom): self
        {
            $this->nom = $nom;
    
            return $this;
        }
    }
    
    Maintenant je souhaite remonter toutes les dépenses ou les crédits du site web par catégorie. Par exemple je veux toutes les dépenses liées à la catégorie Hébergement ou bien je veux toutes les dépenses de la catégorie licence logiciel. Du coup dans le repository j'ai testé la requête suivante
    public function findTotalByCategorieDepenses($value): array {
         return $this->createQueryBuilder('m')
                      ->andWhere('m.typedepenses = :val')
                      ->setParameter('val', $value)
                      ->select('SUM(m.montant) as total, m.date')
                      ->groupBy('m.catcredit')
                      ->getQuery()
                      ->getResult()
            ;
        }
    cette requête me remonte bien les dépenses par catégorie :
    $test = $mouvements_finances_repository->findTotalByCategorieDepenses('debit');
    
    dd($test);
    GlobalStatsController.php on line 48:
    array:5 [▼
      0 => array:2 [▼
        "total" => 8503.0
        "date" => DateTime @1672499160 {#1027 ▼
          date: 2022-12-31 15:06:00.0 UTC (+00:00)
        }
      ]
      1 => array:2 [▼
        "total" => 24025.0
        "date" => DateTime @1658761020 {#1033 ▼
          date: 2022-07-25 14:57:00.0 UTC (+00:00)
        }
      ]
      2 => array:2 [▼
        "total" => 1800.0
        "date" => DateTime @1666882920 {#996 ▼
          date: 2022-10-27 15:02:00.0 UTC (+00:00)
        }
      ]
      3 => array:2 [▶]
      4 => array:2 [▶]
    ]
    c'est top, j'ai de quoi faire avec ça. Mais je veux également remonter le nom des catégories...et la je n'y arrive pas car je dois utiliser un champ qui est mappé avec l'entité CategoriesDepenses. Comment il y a june relation ManyToOne entre ces deux entités, je n'arrive pas à remonter l'information nom de la catégorie...j'ai testé pas mal de choses sans résultat
    voici ci dessous les images de ma base de données
    table mouvements_finances
    table categories_depenses
    Merci d'avance pour votre aide et vos réponses

    -
    Edité par doogie1 3 février 2023 à 20:00:15

    • Partager sur Facebook
    • Partager sur Twitter
      3 février 2023 à 20:14:08

      Bonjour,

      Et si tu ajoutes m.catdebit en ligne 5 de ta méthode ?

      A+

      • Partager sur Facebook
      • Partager sur Twitter
        3 février 2023 à 20:44:38

        merci monkey3d de ta réponse

        J'ai déjà testé ça et j'ai l'erreur suivante

        [Semantical Error] line 0, col 42 near 'catdebit FROM': Error: Invalid PathExpression. Must be a StateFieldPathExpression.


        comme catdebit est une relation ManyToOne de l'entité, il me remonte cette erreur. En cherchant sur le net j'ai pu voir que je devais ajouter des join dans ma requête mais je n'ai pas réussi à la faire fonctionner. J'ai trouvé àa sur un forum mais je ne vois pas comment l'adapter dans ma requête :

        https://www.developpez.net/forums/d1937663/php/bibliotheques-frameworks/symfony/doctrine2/doctrine2-erreur-requete-semantical-error-line-0-col-42-near-xx-error-invalid-pathexpress-must-be/

        -
        Edité par doogie1 3 février 2023 à 20:51:29

        • Partager sur Facebook
        • Partager sur Twitter
          3 février 2023 à 23:59:48

          Salut

          Il faut effectivement ajouter une jointure. Pour ce faire, tu as les méthodes join(), innerJoin() et leftJoin(), je pense que c'est la deuxième qui t'intéresse. Attention cependant : on lie sur des propriétés avec l'ORM, oublie les notions de table et de colonne.
          Comme premier argument, il faut donc mettre le nom de la propriété qui contient une autre entité, préfixé de ce que tu as mis comme valeur pour createQueryBuilder() et un point entre deux.
          Comme second argument, il faut une chaîne de caractères qui permettra de représenter la nouvelle entité dans la requête. Tout comme ton 'm' représente MouvementsFinances, il faudrait quelque chose qui puisse représenter CategoriesDepenses. Ensuite de quoi, il peut être pratique d'ajouter addSelect() avec, en paramètre, ce qu'on a mis en second paramètre de la fonction de jointure. Quelques explications plus complètes (mais un peu hors contexte) ici

          Si tu as un bon environnement de développement, l'auto-complétion pourrait t'aider.

          • Partager sur Facebook
          • Partager sur Twitter
            4 février 2023 à 7:22:41

            L'ajout que je proposais était une piste bien-sûr trop simpliste et il faut ajouter un peu de glue : +1 Ymox

            A+

            -
            Edité par monkey3d 4 février 2023 à 7:23:17

            • Partager sur Facebook
            • Partager sur Twitter
              7 février 2023 à 19:27:11

              merci Ymox, ça avance bien :) suite à ton aide. Voici ma nouvelle requête

              public function findTotalByCategorieDepenses($value): array {
                   return $this->createQueryBuilder('m')
                     ->andWhere('m.typedepenses = :val')
                     ->setParameter('val', $value)
                     ->join('m.catdebit','c')
                     ->addSelect('c.nom, SUM(m.montant) as total, m.date')
                     ->groupBy('m.catdebit')
                     ->getQuery()
                     ->getResult()
                      ;
                  }

              et du coup j'ai ce tableau en résultat

              3 => array:4 [▼
                  0 => App\Entity\MouvementsFinances {#862 ▶}
                  "nom" => "Rédaction article"
                  "total" => 34960.0
                  "date" => DateTime @1650465360 {#863 ▶}
                ]
                4 => array:4 [▼
                  0 => App\Entity\MouvementsFinances {#856 ▼
                    -id: 46
                    -montant: 1440.0
                    -typedepenses: "debit"
                    -remarque: "Transfert ndd O2switch"
                    -mouvementwebsite: Proxies\__CG__\App\Entity\Website {#785 ▶}
                    -catcredit: null
                    -catdebit: Proxies\__CG__\App\Entity\CategoriesDepenses {#855 ▶}
                    -date: DateTime @1650033360 {#859 ▶}
                  }
                  "nom" => "Hébergement"
                  "total" => 12083.0
                  "date" => DateTime @1650033360 {#858 ▶}

              le petit souci c'est que cette requête me retourne toute l'entité MouvementsFinances alors que j'ai fait une restriction dans mon addselect. Je ne comprends pas pourquoi

              merci beaucoup j'ai bien avancé :)


              • Partager sur Facebook
              • Partager sur Twitter
                7 février 2023 à 21:16:57

                Tu as cru faire une restriction dans addSelect() (qui, comme son nom l'indique, ajoute des données à sélectionner), or createQueryBuilder('m') implique déjà de sélectionner l'entier de l'entité. De plus, je pense que tu aurais des surprises si tu regardais le contenu de l'entité CategorieDepense.

                Pour sélectionner seulement certaines propriétés de l'entité, tu as la syntaxe PARTIAL alias.{id,autrePropriete,etc.} qui se met dans select() ou addSelect(). Tu ne peux pas ne pas demander l'ID de l'entité sauf mise à jour que je n'ai pas suivi.

                Ensuite, ce serait peut-être plus pratique pour toi si tu utilisais getArrayResult() plutôt que getResult(), à toi de voir.

                • Partager sur Facebook
                • Partager sur Twitter
                  8 février 2023 à 18:26:51

                  ok merci pour ton retour. Alors avant de tester ta préconisation j'ai fais ceci avec succès

                  public function findTotalByCategorieDepenses($value): array {
                       return $this->createQueryBuilder('m')
                         ->select('SUM(m.montant) as total, m.date')
                         ->andWhere('m.typedepenses = :val')
                         ->setParameter('val', $value)
                         ->join('m.catdebit','c')
                         ->addSelect('c.nom')
                         ->groupBy('m.catdebit')
                         ->getQuery()
                         ->getResult()
                          ;
                      }

                  en fait je fais une restriction sur l'entité MouvementsFinances en ajoutant un select et après mon join j'ajoute une nouvelle restriction avec addselect. Cela fonctionne bien

                  d'ailleurs j'ai gardé aussi getResult() au lieu de getArrayResult(). Pourquoi tu préfères cette dernière méthode ?

                  Merci beaucoup à toi pour ton aide

                  • Partager sur Facebook
                  • Partager sur Twitter
                    8 février 2023 à 21:04:57

                    Ce n'est pas une restriction que de mettre addSelect('c.nom'), je te laisse regarder le contenu de ton entité CategoriesDepenses. A mon avis, tu as plus de données récupérées que ce à quoi tu t'attends.

                    getResult() va conserver les objets, alors que getArrayResult() ne fait pas cette conversion. Pour ce que tu récupères, du fait de la somme et du regroupement, mais aussi du peu de propriétés de deux entités différentes je pense que ce serait plus pratique.

                    • Partager sur Facebook
                    • Partager sur Twitter

                    Utiliser un champ manytoone dans un querybuilder

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown