I have an entity Client and this entity needs to have an association to another Client with information about relationship. This entity is ClientRelationship.
I need to have this asociation in one property, because I want to use symfony form with collection type field.
Problem is that I do not know how to write it to have that association in one Client property.
Now I have in Client two asociation properties, but that is my problem. In form on one side is relationship visible, but on other Client its not.
Client entity:
/**
* #ORM\OneToMany(targetEntity="ClientRelationship", mappedBy="leftClient", cascade={"persist", "remove"})
*/
private $leftRelationships;
/**
* #ORM\OneToMany(targetEntity="ClientRelationship", mappedBy="rightClient")
*/
private $rightRelationships;
ClientRelationship entity:
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length = 255)
* #Assert\Length(max = 255)
* #Assert\NotBlank()
*/
private $relationship;
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="leftRelationships")
* #ORM\JoinColumn(name="leftClient_id", referencedColumnName="id")
*/
private $leftClient;
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="rightRelationships")
* #ORM\JoinColumn(name="rightClient_id", referencedColumnName="id")
*/
private $rightClient;
EDIT: added forms
ClientType form:
$builder->add('leftRelationships', 'bootstrap_collection', array(
'type' => new ClientRelationshipType(),
'label' => 'relationships',
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
'add_button_text' => 'add',
'delete_button_text' => 'remove',
'options' => array(
'businessCase' => $options['businessCase'],
'client' => $options['client'],
),
));
ClientRelationshipType form:
$builder->add('rightClient', 'entity', array(
'label' => 'client',
'class' => 'MyProject\CoreBundle\Entity\Client',
'property' => 'name',
'query_builder' => function (ClientRepository $er) use ($options) {
$qb = $er->createQueryBuilder('c');
if ($options['businessCase'] instanceof BusinessCase) {
$qb->andWhere($qb->expr()->in('c.id', ':clients'));
$qb->setParameter(':clients', $options['businessCase']->getClients()->toArray());
}
if ($options['client'] instanceof Client) {
$qb->andWhere($qb->expr()->neq('c.id', ':client'));
$qb->setParameter(':client', $options['client']);
}
return $qb;
},
));
$builder->add('relationship', 'text', array(
'label' => 'relationship',
));
Related
After several weeks of research, I turn to you to try to understand the QueryBuilder part and implement it on my application.
Concretely, here is what I would like to do:
Depending on the agent(s) chosen by the user, display targets with the same nationality.
My agent entity and my target entity each have a nationality. They are both connected thanks to Doctrine on my Mission entity.
I think I should use NOT IN in my request but don't know how to do it. I show you what I did without results.
/**
* Récupère les nationalités de l'agent
*/
public function findNationality()
{
$this
->createQueryBuilder ('m')
->select ('*')
->join('m.agents', 'a')
->join('m.cibles', 'c')
->where('a.nationality = c.nationality')
->getQuery()
->getResult();
}
My MissionType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('description')
->add('code_name')
->add('country')
->add('speciality', EntityType::class, [
'label' => 'Choisir une spécialité: ',
'placeholder' => 'Choisir une spécialité',
'class' => Specialite::class,
'choice_label' => 'name'
])
->add('agents', EntityType::class, [
'label' => 'Choisir un ou des agent(s): ',
'class' => Agent::class,
'choice_label' => 'identification_code',
'multiple' => true,
'expanded' => true
])
->add('contacts', EntityType::class, [
'label' => 'Selectionner le(les) contact(s): ',
'class' => Contact::class,
'choice_label' => 'code_name',
'multiple' => true,
'expanded' => true
])
->add('cibles', EntityType::class, [
'label' => 'Selectionner la(les) cible(s): ',
'class' => Cible::class,
'choice_label' => 'code_name',
'multiple' => true,
'expanded' => true
])
->add('planques', EntityType::class, [
'label' => 'Selectionner la(les) planque(s): ',
'class' => Planque::class,
'choice_label' => 'code',
'multiple' => true,
'expanded' => true,
])
->add('start_date', DateType::class, [
'widget' => 'single_text'
])
->add('end_date', DateType::class, [
'widget' => 'single_text'
])
->add('status', EntityType::class, [
'label' => 'Statut de la Mission: ',
'class' => Status::class,
'choice_label' => 'name'
])
->add('type', EntityType::class, [
'label' => 'Type de Mission: ',
'class' => TypeMission::class,
'choice_label' => 'name'
]);
}
My Mission Entity
class Mission
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $title;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string", length=255)
*/
private $code_name;
/**
* #ORM\Column(type="string", length=255)
*/
private $country;
/**
* #ORM\Column(type="date")
*/
private $start_date;
/**
* #ORM\Column(type="date")
*/
private $end_date;
/**
* #ORM\OneToMany(targetEntity=Agent::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $agents;
/**
* #ORM\OneToMany(targetEntity=Cible::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $cibles;
/**
* #ORM\OneToMany(targetEntity=Contact::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $contacts;
/**
* #ORM\OneToMany(targetEntity=Planque::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $planques;
/**
* #ORM\ManyToOne(targetEntity=Specialite::class, inversedBy="missions", cascade={"persist", "merge"})
*/
private $speciality;
/**
* #ORM\ManyToOne(targetEntity=Status::class, inversedBy="missions", cascade={"persist", "merge"})
*/
private $status;
/**
* #ORM\ManyToOne(targetEntity=TypeMission::class, inversedBy="missions", cascade={"persist", "merge"})
*/
private $type;
public function __construct()
{
$this->agents = new ArrayCollection();
$this->cibles = new ArrayCollection();
$this->contacts = new ArrayCollection();
$this->planques = new ArrayCollection();
}
I would like the list of targets to update dynamically according to the choice of agents.
Any help will be welcome and I thank you in advance because I admit going around in circles and no longer know where I am.
In a Symfony 4 application that I've been asked to work on I am attempting to enforce a uniqueness constraint on the name of my program (a course of instruction, not software) within a given company. Despite the attempted constraint, the app happily lets me create a program with the same name as one that already exists in the given company.
I've found various contradictory examples of how to set up a composite constraint, and I've read through the many StackOverflow questions on this topic to no avail.
The relevant code for my entity, program.php:
<?php
namespace Domain\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Domain\AdminBundle\Service\Helper\RouteListHelper;
use Domain\CoreBundle\Repository\ProgramRepository as ProgramRepo;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use JsonSerializable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Program
* #ORM\Entity(repositoryClass="Domain\CoreBundle\Repository\ProgramRepository")
* #ORM\Table(name="programs")
* #UniqueEntity(
* fields={"name","company"},
* errorPath = "name",
* message="A program by that name already exists for this company."
* )
* #ORM\HasLifecycleCallbacks()
*/
class Program implements JsonSerializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Assert\NotBlank(message="Program Name should not be empty")
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
*/
protected $company;
...
and my addProgramType.php:
<?php
namespace Domain\AdminBundle\Form;
use Domain\CoreBundle\Repository\UserRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
/**
* Class AddProgramType
*/
class AddProgramType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$params = array(
'name' => array(
'label' => 'Program name:',
'attr' => array('class' => 'base-box'),
),
'isEnabled' => array(
'label' => false,
'attr' => array(
'checked' => 'checked',
),
),
'isRoiCalculating' => array(
'label' => false,
),
'duration' => array(
'label' => 'Duration:',
'class' => 'DomainCoreBundle:Duration',
'query_builder' => function (EntityRepository $er) use ($options) {
return $er->getDurationsQb($options['company']);
},
'choice_label' => 'uniqueName',
'attr' => array(
'class' => 'base-box',
),
),
'sessionTypes' => array(
'class' => 'DomainCoreBundle:SessionType',
'query_builder' => function (EntityRepository $er) use($options) {
return $er->getAllSessionTypesQb($options['company']);
},
'choice_label' => 'name',
'multiple' => true,
'label' => 'Session Types:',
'attr' => array(
'class' => 'multiselect-dropdown multiselect-dropdown-session-types',
'required' => 'required',
'multiple' => 'multiple',
),
),
'users' => array(
'required' => false,
'class' => 'DomainCoreBundle:User',
'choices' => $options['userRepo']->findByRoles(
array(UserRepository::ROLE_ADMIN,UserRepository::ROLE_COMPANY_ADMIN),
$options['company'],
false),
'choice_label' => 'getFullName',
'multiple' => true,
'label' => 'Access to admins:',
'attr' => array(
'class' => 'multiselect-dropdown multiselect-dropdown-users',
'multiple' => 'multiple',
),
),
);
$builder
->add('name', null, $params['name'])
->add('isEnabled', CheckboxType::class, $params['isEnabled'])
->add('isRoiCalculating', CheckboxType::class, $params['isRoiCalculating'])
->add('duration', EntityType::class, $params['duration'])
->add('sessionTypes', EntityType::class, $params['sessionTypes'])
->add('users', EntityType::class, $params['users']);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'Domain\CoreBundle\Entity\Program'));
$resolver->setRequired(array('company', 'userRepo'));
}
/**
* Return form name
*
* #return string
*/
public function getBlockPrefix()
{
return 'add_program';
}
}
While the application enforces the NotBlank constraint on the name correctly, it doesn't enforce the uniqueness of name + company.
Any suggestions?
[UPDATE] Looks like I set company after the isValid() call, thanks BoShurik for the catch. Here's the relevant controller code showing my mistake:
/**
* Add new program
*
* #param Request $request
*
* #return Response
*/
public function addNewAction(Request $request)
{
$form = $this->createForm(AddProgramType::class, null, array('company'=>$this->getCurrentCompany(),
'userRepo' =>$this->em->getRepository('DomainCoreBundle:User')));
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$company = $this->getCurrentCompany();
$program = $form->getData();
$program->setCreatedDate(new \DateTime());
$program->setCompany($company);
...
If you want to add the same check at database level you should use the #UniqueConstraint annotation in the Table() declaration and give a name to the new index.
Something like:
/**
* Program
* #ORM\Entity(repositoryClass="Domain\CoreBundle\Repository\ProgramRepository")
* #ORM\Table(name="programs", uniqueConstraints={#ORM\UniqueConstraint(name="IDX_PROGRAM_COMPANY", columns={"name", "company_id"})})
* #UniqueEntity(
* fields={"name","company"},
* errorPath = "name",
* message="A program by that name already exists for this company."
* )
* #ORM\HasLifecycleCallbacks()
*/
class Program implements JsonSerializable
```
As a company field is not manager by your form, you need to set its value before form validation:
public function addNewAction(Request $request)
{
$program = new Program();
$program->setCompany($this->getCurrentCompany());
$form = $this->createForm(AddProgramType::class, $program, array('company' => $this->getCurrentCompany(),
'userRepo' => $this->em->getRepository('DomainCoreBundle:User')));
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$program = $form->getData();
$program->setCreatedDate(new \DateTime());
}
}
}
I have a problem with one of my forms. The form has to create a new Colle entity and link some other Colle entities to it.
When I submit it, a new entity is created for each item in collesEnfants collection field. The new entity created is correctly linked to the parent and has the right 'ordre' field but it's a newly created entity and not the entity I've selected.
My form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom', TextType::class,['label' => 'Nom de la colle'])
->add('collesEnfants', CollectionType::class,
['label' => false,
'entry_type' => SousColleFormType::class,
'required' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'PACES\ColleBundle\Entity\Colle'
]);
}
SousColleFormType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('matiere', EntityType::class, [
'class' => 'PACESColleBundle:Matiere',
'attr' => ['class'=> 'matiere'],
'choice_label' => 'name',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez une matière',
'mapped' => false])
->add('nom', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'group_by' => 'matiere',
'required' => true,
'placeholder' => 'choose.colle'])
->add('ordre', IntegerType::class,[
'attr'=>['class'=>'ordre'],
'required' => true,
'label' => false]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'PACES\ColleBundle\Entity\Colle'
]);
}
Controller :
$formColleMere = $this->createForm(AjoutSuperColleFormType::class, $colle);
$formColleMere->add('submit', SubmitType::class, ['label' => 'Créer']);
$formColleMere->handleRequest($request);
if ($formColleMere->isSubmitted() && $formColleMere->isValid()) {
$collesEnfants = $formColleMere->get('collesEnfants')->getData();
foreach ($collesEnfants as $enfant) {
$colle->addColleEnfant($enfant);
}
if (!$colle->getCollesEnfants()->isEmpty()) {
$em->persist($colle);
$em->flush();
}
Colle entity :
class Colle
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Colle", mappedBy="colleMere", cascade={"persist"})
* #ORM\OrderBy({"ordre" = "asc"})
*/
private $collesEnfants;
/**
* #ORM\ManyToOne(targetEntity="Colle", inversedBy="collesEnfants", cascade={"persist"})
* #ORM\JoinColumn(name="colleMere_id", referencedColumnName="id")
*/
private $colleMere;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*/
protected $nom;
{........}
/**
* #ORM\ManyToOne(targetEntity="PACES\ColleBundle\Entity\Matiere", inversedBy="colles", cascade={"persist"})
* #ORM\JoinColumn(name="matiere_id", referencedColumnName="id")
* #ORM\OrderBy({"name" = "ASC"})
*/
protected $matiere;
/**
* Cet attribut sert aux 'super colles' qui sont le résultat d'une fusion de colles d'une même UE
* #var integer
* #ORM\Column(name="ordre", type="integer", nullable=true)
*/
protected $ordre;
I succeeded in doing what I want by adding 'mapped' => false to collesEnfants field.
I also changed these lines in the Controller :
$collesEnfants = $formColleMere->get('collesEnfants')->getData();
foreach ($collesEnfants as $enfant) {
$colle->addColleEnfant($enfant);
}
To :
$collesEnfants = $formColleMere->get('collesEnfants')->getData();
foreach ($collesEnfants as $enfant) {
$colle->addColleEnfant($enfant['nom']);
$enfant['nom']->setOrdre($enfant['ordre']);
}
I am devolping an application in Symfony 2.7, I generated the CRUD from a PRODUCT TABLE (InProducto) which is related OneToMany to another table (InUnidadMedida).
When I open Edit Form, the value in ENTITY FIELD (which is a select field from UNIDAD DE MEDIDA table) always appears the first option of related table (UNIDAD DE MEDIDA). And It suppose to get the value in the field of the table INPRODUCTO
InProductoType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nombre')
->add('descripcion')
->add('unidadMedida', 'entity', array(
'class' => 'NivalInventarioBundle:InUnidadMedida',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.nombre', 'ASC');
},
'choice_label' => 'nombre',
'by_reference' => false,
'property' => 'type',
'expanded' => false,
'multiple' => false
))
}
Controller:
private function createEditForm(InProducto $entity)
{
$form = $this->createForm(new InProductoType(), $entity, array(
'action' => $this->generateUrl('inproducto_update', array('id' => $entity->getIdProducto())),
'method' => 'PUT',
));
$form->add('submit', 'submit', array('label' => 'Guardar'));
return $form;
}
Producto table (Entity)
/**
* InProducto
*
* #ORM\Table(name="in_producto")
* #ORM\Entity
*/
class InProducto
{
/**
* #ORM\ManyToOne(targetEntity="InSubLinea", inversedBy="InProducto")
* #ORM\JoinColumn(name="id_sub_linea", referencedColumnName="id_sub_linea")
*/
protected $subLinea;
/**
* #ORM\ManyToOne(targetEntity="InUnidadMedida", inversedBy="InProducto")
* #ORM\JoinColumn(name="id_unidad_medida", referencedColumnName="id_unidad_medida")
*/
protected $unidadMedida;
/**
* #var integer
*
* #ORM\Column(name="id_producto", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
Unidad de medida TABLE (entity)
/**
* InUnidadMedida
*
* #ORM\Table(name="in_unidad_medida")
* #ORM\Entity
*/
class InUnidadMedida
{
/**
* #ORM\OneToMany(targetEntity="InProducto", mappedBy="InUnidadMedida")
*/
protected $InProducto;
public function __construct()
{
$this->InProducto = new ArrayCollection();
}
The form type will be guessed automatically by Symfony, if you've mapped these entities correctly.
So make it just
->add('unidadMedida', null, array(
'choice_label' => 'nombre',
'expanded' => false,
'multiple' => false
))
And there is no such option as property. Did you mean property_path?
After hours of scratching head, it was very simple, I add the following properties to my fields:
'by_reference' => true,
'mapped' => true,
Thanks Dmitry Malyshenko for your time.
I have a simple question,
I have two tables in relation many to many, Post and Category,
in an intact form PostType a collection of form CategoryType, but here the problems begin ..
I followed the instructions on the cookbook collection form to persist the data, I just do not get the desired result ..
Here's the code:
class Post
{
/**
*
* #ORM\ManyToMany(targetEntity="Categories", inversedBy="posts", cascade={"persist", "remove"})
* #ORM\JoinTable(name="AnCat",
* joinColumns={
* #ORM\JoinColumn(name="post_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="categories_id", referencedColumnName="id")
* }
* )
**/
protected $categories;
public function __construct()
{
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addCategory($categories)
{
foreach ($categories as $category) {
$category->addPosts($this);
}
$this->categories[] = $categories;
}
class Categories
{
/**
*
* #ORM\ManyToMany(targetEntity="Post", mappedBy="categories")
*/
protected $posts;
public function __construct()
{
$this->posts = new ArrayCollection();
}
/**
*
* #param Post $post
* #return Categories
*/
public function addPosts(Post $posts)
{
// I tried it but I get the same result!
/*if (!$this->posts->contains($posts)) {
$this->posts->add($posts);
}*/
$posts->addCategory($this);
$this->posts[] = $posts;
}
class PostType extends AbstractType
{
->add('Categories', 'collection', array('type' => new CategoriesType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__categ__',
'by_reference' => false
))
class CategoriesType extends AbstractType
{
->add('category', 'entity', array(
'attr' => array('class' => 'cat'),
'class' => 'MyBusinessBundle:Categories',
'property' => 'category',
'label' => 'Categories'
))
The problem is that inserts a new field Category, instead of creating a simple relationship Post-Category.
I don't understand where I'm wrong ..
In your postType, change collection type into entity Type
class PostType extends AbstractType
{
$builder->add('Categories', 'entity',
array( 'label' => 'Categories',
'required' => false,
'expanded' => true,
'class' => 'xxx\xxxBundle\Entity\Categories',
'property' => 'title',
'multiple' => true,
));
In your post creation form you will have checkboxes with categories. If you want a multi select field, change expanded by false