Sélection multiple d'une entité

a marqué ce sujet comme résolu.

Bonjour à tous,

Je me retrouve dans une impasse. J'ai ce schéma :

Image utilisateur

Je voudrai maintenant créer un formulaire pour que quand je sélectionne un utilisateur, je puisse lui attribuer un pack. Mon problème et qu'une fois le pack consommé, l'utilisateur peut acheter un autre pack, mais cet autre pack peut être le même que le premier et je vois pas comment gérer ça avec un champ de type entity.

Je sais qu'il y a aussi les forms collection, mais ça ne répond pas à mon besoin, je ne veux pas ajouter un formulaire, mais bien juste sélectionner plusieurs fois la même entité.

Merci de m'avoir lu :)

Salut !

[…] je ne veux pas ajouter un formulaire, mais bien juste sélectionner plusieurs fois la même entité.

Freeza

Juste pour que je sois sûr d'avoir compris, comment aimerais-tu que cela se présente ? J'imagine que tu as déjà une idée en tête, est-ce que tu pourrais nous la partager ?

+0 -0

Salut !

[…] je ne veux pas ajouter un formulaire, mais bien juste sélectionner plusieurs fois la même entité.

Freeza

Juste pour que je sois sûr d'avoir compris, comment aimerais-tu que cela se présente ? J'imagine que tu as déjà une idée en tête, est-ce que tu pourrais nous la partager ?

Ymox

Oui enfaite, j'ai un formulaire pour créer un utilisateur, nom, prénom… Dans ce formulaire ou ailleurs peu importe, j'aimerai pouvoir sélectionner un pack. Enfaisant cela, j'attribu un pack et donc des services à cet utilisateur (Un pack contient un ou plusieurs services). Bon ça c'est facile. La où ça se corse, c'est qu'une fois que l'utilisateur a consommé tous les services, il doit pouvoir racheter un pack et potentiellement le même pack.

J'ai pas d'idée précise de la présentation, ça peut être dans le formulaire de création d'un utlisateur (c'est mieux), comme ça peut-être dans un autre formulaire si c'est plus simple.

L'un comme l'autre, ça ne changera que très peu le traitement, à mon avis. Seulement, un utilisateur ne sera créé qu'une fois, tandis que lui assigner des packs, ce sera fait souvent. Je préconiserais deux formulaires séparés.

En conséquence, je pense à un formulaire qui retourne un utilisateur, mais qui ne contiendrait qu'un seul champ de type collection. Cette collection contiendra des formulaires pour les Utilisateurs_Pack_Service, eux-même avec un seul champ : la liste de packs (simple type entity). Permission d'ajouter, pas de supprimer.
Là où ça se corse, c'est effectivement au niveau de la validation. Il faudrait une contrainte qui me fait penser à UniqueEntity, mais qui, parmi les tuples user_id-pack_id-comsumed, ne permette que l'unicité des tuples où consumed est à false (j'imagine que c'est un champ booléen, j'ai raison ? Si non, je continue avec cette idée quand-même  :P ). L'idée étant donc que toutes les instances consommées ne posent pas de problème, même si l'utilisateur a consommé plusieurs fois le même pack (et que c'est le préféré de tous les utilisateurs, donc tous l'ont consommé au moins deux fois), aucun utilisateur ne peut avoir deux mêmes packs non-consommés en même temps. En bref, juste vérifier s'il n'existe pas déjà en base de données un tuple user_id-pack_id-false.

+0 -0

Mmm…

Un pack peut contenir plusieurs services, mais ces services sont à la carte ?

En fait, ce que je ne comprends pas, c'est pourquoi lier avec une "entité intermédiaire" plutôt qu'avec un pack directement. Parce que tu lies bien un utilisateur à un pack et le pack contient des services. Ce n'est pas la même chose que de lier à un pack et des services.

+0 -0

Non les services ne sont pas à la carte, on ne peut acheter que des packs. La relation vient du fais qu'il faut que je sache si un service à été consommé ou pas et la lisaison avec l'entité intermédiraire plutôt que Pack est pour garantir qu'on est pas un service qui ne soit pas présent dans le pack dans le même tuple.

Je continue ma réflexion : pourquoi ne pas lier les utilisateurs directement aux services des packs ?
Tu choisirais un pack dans le formulaire, le champ ne serait pas mappé, et à la soumission tu récupères les services du pack que tu injectes dans les données pour créer les entités intermédiaires.

[…] la lisaison avec l'entité intermédiraire plutôt que Pack est pour garantir qu'on est pas un service qui ne soit pas présent dans le pack dans le même tuple.

Freeza

Je ne suis pas certain d'avoir compris.
Si c'est pour éviter d'avoir plusieurs fois le même service dans un pack, il n'y a pas besoin d'une relation intermédiaire. Si c'est pour s'assurer qu'un service soit bien présent dans un pack, non-plus.
Si c'est pour éviter que deux packs ayant le même service soient souscrits en même temps, pourquoi pas. Idem si c'est pour éviter des doublons de services souscits par différents packs.

Mais je me pose la question : est-ce que ce sont les services qui sont consommés, ou les packs ?
Doit-on attendre que tous les services soient consommés pour pouvoir prendre un nouveau pack identique ?

+0 -0

Effectivement, j'ai pensé à faire un champ non mappé, puis faire le reste manuellement. Ceux sont les services qui sont consomés. Oui y a peut-être pas besoin de passé par la relation en faite, c'est que j'aimerai bien affiché un formulaire avec les packs et pour chaque packs, les services associés et une case à cocher pour indiqué la consomation ou non du service.

Excuse-moi de ne poser que des questions, mais…

Est-ce que tu dois pouvoir cocher les services d'un pack, ou c'est juste une information que tu cherches à transmettre en parlant de cocher les cases des services, et on choisit des packs ?

+0 -0

Oui je souhaite pouvoir cocher les services si ils sont consomés. Je voudrai :

Jean Dupont

Pack Premium

  • Service 1 x
  • Service 2 x

Pack Premium

  • Service 1 x
  • Service 2

Enfaite j'ai besoin de la relation avec Pack_Service sinon je ne peux pas différencier les Packs.

+0 -0

Bon, ce que je ferais avec les informations que j'ai maintenant : je lierais les utilisateurs aux services, avec ta OneToManyToOne. Dans le formulaire de l'entité intermédiaire, je mettrais un champ non-mappé qui liste les packs, et le champ mappé qui liste les services. Les services ne sont pas modifiables "humainement", mais par JavaScript, selon le(s) pack(s) choisi(s).

Enfaite j'ai besoin de la relation avec Pack_Service sinon je ne peux pas différencier les Packs.

Freeza

Je ne comprends toujours pas. Un service peut être dans deux packs, et ces deux packs peuvent être souscrits simultanément ? Il faut pouvoir différencier le service de quel pack est utilisé ?

+0 -0

Bon, ce que je ferais avec les informations que j'ai maintenant : je lierais les utilisateurs aux services, avec ta OneToManyToOne. Dans le formulaire de l'entité intermédiaire, je mettrais un champ non-mappé qui liste les packs, et le champ mappé qui liste les services. Les services ne sont pas modifiables "humainement", mais par JavaScript, selon le(s) pack(s) choisi(s).

Enfaite j'ai besoin de la relation avec Pack_Service sinon je ne peux pas différencier les Packs.

Freeza

Je ne comprends toujours pas. Un service peut être dans deux packs, et ces deux packs peuvent être souscrits simultanément ? Il faut pouvoir différencier le service de quel pack est utilisé ?

Ymox

J'aimerai bien oui, savoir à quel pack appartient le service.

Un utilisateur peu très bien acheter un deuxième pack différent mais qui contient un (ou plusieurs) service en commun avec le premier service acheté. Je pourrai après faire des statistiques sur les packs et non pas que sur les services.

Sinon effectivement, la solution serait de relier directement à l'entité Service. Si vraiment je trouve pas comment faire, je partirai la dessus, mais je pense pas que ma problèmatique soit si originale que ça, d'autre l'on surement déjà eu, donc je garde espoire. :)

Tu ne m'as toujours pas répondu sur le côté simultané des différents packs mais avec le même service  ;)
Parce que pour moi, en un instant T, le service est consommé ou non, quel que soit le pack, donc après, je ne verrais pas de raison de dire que c'est le service de tel pack plutôt que l'autre s'il y en a deux en même temps.

Note cependant que ma solution avec la liaison directement vers les services peut quand-même s'adapter avec les entités Pack_Service, et du coup, tu pourrais même avoir les deux champs Pack et Services mappés.

+0 -0

J'avais un peu mis de côté ce projet, je suis de retour et j'ai toujours un peu de mal à créer mon formulaire.

Voici mes entités (User est remplacé par Member):

Member :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
/**
 * Member
 *
 * @ORM\Table(name="`Member`")
 * @ORM\Entity(repositoryClass="MemberRepository")
 */
class Member
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="firstName", type="string", length=50, nullable=true)
     */
    private $firstName;

    /**
     * @var object
     *
     * @ORM\oneToMany(targetEntity="MemberPackService", mappedBy="member", cascade={"persist", "remove"})
     */
    private $services;

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
/**
 * MemberService
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="MemberPackServiceRepository")
 */
class MemberPackService
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var boolean
     *
     * @ORM\Column(name="consumed", type="boolean")
     */
    private $consumed;

    /**
     * @var object
     *
     * @ORM\ManyToOne(targetEntity="Member", inversedBy="services")
     * @ORM\JoinColumn(name="member_id", referencedColumnName="id")
     */
    private $member;

    /**
     * @var object
     *
     * @ORM\ManyToOne(targetEntity="PackService", inversedBy="members")
     * @ORM\JoinColumn(name="pack_service_id", referencedColumnName="id")
     */
    private $service;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
/**
 * PackService
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="PackServiceRepository")
 */
class PackService
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var object
     *
     * @ORM\ManyToOne(targetEntity="Pack", inversedBy="services")
     * @ORM\JoinColumn(name="pack_id", referencedColumnName="id")
     */
    private $pack;

    /**
     * @var object
     *
     * @ORM\ManyToOne(targetEntity="Service", inversedBy="packs")
     * @ORM\JoinColumn(name="service_id", referencedColumnName="id")
     */
    private $service;

    /**
     * @var Object
     * 
     * @ORM\oneToMany(targetEntity="MemberPackService", mappedBy="service", cascade={"persist", "remove"})
     */
    private $members;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
/**
 * Pack
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="PackRepository")
 * @ORM\HasLifecycleCallbacks
 */
class Pack
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=50)
     */
    private $name;

    /**
     * @var float
     *
     * @ORM\Column(name="price", type="float")
     */
    private $price;

    /**
     * @var object
     * 
     * @ORM\OneToMany(targetEntity="PackService", mappedBy="pack", cascade={"persist", "remove"})
     */
    private $services;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
/**
 * Service
 *
 * @ORM\Table(name="`Service`")
 * @ORM\Entity(repositoryClass="ServiceRepository")
 */
class Service
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=50)
     */
    private $name;

    /**
     * @var PackService
     * 
     * @ORM\OneToMany(targetEntity="PackService", mappedBy="service", cascade={"persist", "remove"})
     */
    private $packs;
}

Ce que je voudrai c'est que pour un Member, je puisse ajouter un ou plusieurs Pack. Quand j'ajoute un Pack, j'ai la liste des Services associés qui apparaissent avec le nom du Service est un case à cocher pour savoir si le service est consommé ou pas.

Voici mes formulaires :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

class MemberType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', 'text', array(
                'label' => 'Prénom : ',
            ))
            ->add('packs', 'entity', array(
                    'class' => 'MyBundle:Pack',
                    'property' => 'name',
                    'label' => 'Packs',
                'mapped' => false
            ))
            ->add('services', 'collection', array(
                'type' => new MemberPackServiceType(),
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
            ))
    }

    public function getName()
    {
        return 'member';
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

class MemberPackServiceType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
        /*->add('service', 'collection', array(
                'type' => new PackServiceType(),
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
        ))*/
        ->add('service', 'entity', array(
                'class' => 'MyBundle:PackService',
                'property' => 'service.name',
                'label' => 'Service : ',
                'read_only' => true,
        ))
        ->add('consumed', 'checkbox', array(
                'label' => 'Consommé',
        ))
    }



    public function getName()
    {
        return 'memberPackService';
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
class PackServiceType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
        ->add('service', 'entity', array(
            'class' => 'MyBundle:Service',
            'property' => 'name',
            'label' => false
        ));
    }

    public function getName()
    {
        return 'packService';
    }

}

J'ai enlevé pas mal de ligne de code (getter, setter…) pour réduire un peu la taille des fichiers.

Merci de votre aide.

Je ne pense pas que tu aies besoin d'un type de champ collection pour les services d'un membre, un champ entity devrait être suffisant.

Ensuite, pour avoir le nom du service ainsi qu'une checkbox cochée ou pas selon la consommation, je pense qu'il va te falloir modifier un peu le template des MemberPackServiceType afin d'avoir d'une part le nom du service comme label, et la case à coché relative à consumed comme widget. Quelque chose dans ce goût-là (à compléter avec le HTML qui te serait nécessaire et à adapter, je ne garantis aucunement que ce code soit directement prêt à l'emploi) :

1
2
3
{% block memberPackService %}
<label for="{{ form.consumed.vars.id }}">{{ form.service.vars.value.name }} {{ form_widget(form.consumed) }}</label>
{% endblock %}

A lier avec cette partie de la documentation

+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