I started project with predefined database structure and generated Entities from database structure using commands from console. I am creating bus company. Bus can have many amenities (i.e. Many buses can have many amenities).
I have a ManyToMany table and for some reason data inside that table is not saving. Other form data is saved in other table but this one still remains empty.
Does someone knows where is the problem?
Here is the code:
BusVehicles.php
/**
* Many Buses have Many Amenities.
* #ORM\ManyToMany(targetEntity="BusVehicles", mappedBy="bus_amenities")
* #ORM\JoinTable(
* name="bus_amenities",
* joinColumns={
* #ORM\JoinColumn(name="bus_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="amenities_id", referencedColumnName="id")
* }
* )
*/
private $amenities;
/**
* Add items
*
* #param \AdminBundle\Entity\Amenities $amenities
*/
public function addAmenities(Amenities $amenities)
{
if ($this->amenities->contains($amenities)) {
return;
}
$this->amenities->add($amenities);
$amenities->addBusAmenities($this);
}
/**
* Remove amenities
*
* #param \AdminBundle\Entity\Amenities $amenities
*/
public function removeAmenities(Amenities $amenities)
{
if (!$this->amenities->contains($amenities)) {
return;
}
$this->amenities->removeElement($amenities);
$amenities->removeBusAmenities($this);
}
/**
* Get Amenities
*
* #return ArrayCollection
*/
public function getAmenities()
{
return $this->amenities;
}
// ...
public function __construct() {
$this->amenities = new ArrayCollection();
}
}
Amenities.php
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="BusVehicles", mappedBy="amenities")
*/
private $bus_amenities;
public function __construct() {
$this->bus_amenities = new ArrayCollection();
}
/**
* Get bus amenities
*
* #return ArrayCollection
*/
public function getBusAmenities()
{
return $this->bus_amenities;
}
/**
* Add bus amenities
*
* #param \AdminBundle\Entity\BusVehicles
*/
public function addBusAmenities(BusVehicles $amenities)
{
if ($this->bus_amenities->contains($amenities)) {
return;
}
$this->bus_amenities->add($amenities);
$amenities->addAmenities($this);
}
/**
* Remove bus amenities
*
* #param \AdminBundle\Entity\BusVehicles
*/
public function removeBusAmenities(BusVehicles $amenities)
{
if (!$this->bus_amenities->contains($amenities)) {
return;
}
$this->bus_amenities->removeElement($amenities);
$amenities->removeAmenities($this);
}
Controller:
class BusController extends Controller
{
/**
* #Template
*/
public function addAction(Request $request)
{
$bus = new BusVehicles();
$form = $this->createForm(BusVehiclesType::class, $bus);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
/* $amenities = $em->getAmenities();
foreach($amenities as $amenitie){
$em->persist($amenitie);
}*/
$em->persist($bus);
$em->flush();
// Adding flash message to our user
$request->getSession()
->getFlashBag()
->add('success', 'New vehicle successfully added');
// return $this->redirectToRoute('bus_add');
}
return [
'form' => $form->createView(),
];
}
}
EDIT 1: Added form
class BusVehiclesType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('licencePlate')
->add('manufacturer')
->add('company', EntityType::class, array(
'class' => 'AdminBundle:Companies',
'choice_label' => 'name',
))
->add('busType', EntityType::class, array(
'class' => 'AdminBundle:BusTypes',
'choice_label' => 'type',
))
->add('emissionClass', EntityType::class, array(
'class' => 'AdminBundle:BusEmissionClasses',
'choice_label' => 'name',
))
->add('fuelType', EntityType::class, array(
'class' => 'AdminBundle:BusFuelTypes',
'choice_label' => 'fuel_type',
))
->add('amenities', EntityType::class, array(
'class' => 'AdminBundle:Amenities',
'choice_label' => 'name',
'multiple' => true,
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AdminBundle\Entity\BusVehicles'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'adminbundle_busvehicles';
}
}
I think the problem is in your BusVehicles.php, Check relation and change as per below:
/**
* Many Buses have Many Amenities.
* #ORM\ManyToMany(targetEntity="Amenities", inversedBy="bus_amenities", cascade={"persist"})
*
*/
private $amenities;
And in Aminities Entity change as per below:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="BusVehicles", mappedBy="amenities", cascade={"persist"})
*/
private $bus_amenities;
Related
I am developing a web app on Symfony. I have 2 entities that is question about, "Line" and "Dosier". So, 1 dosier can have many lines.
Now I'm implementing the CRUD for the Line entity, so Line entity has a "dropdown" with all Dosiers from DataBase.
The problem is that in dropdown are all dosiers from any users, but I need in this dropdown to have just options that has user_id = currentUser_id.
So, my controller action :
/**
* #Route("/add-line", name="addLine")
*/
public function createLineAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
$line = new Line();
$form = $this->createForm(LineType::class, $line);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($line);
$em->flush();
return $this->redirectToRoute('homepage');
}
return $this->render('AppBundle:default:formLines.html.twig', array(
'form' => $form->createView(),
));
}//create dossier action
My LineType (form builder)
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class LineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')->add('dosier')
->add('dosier', EntityType::class, array(
'class' => 'AppBundle:Dosier',
'query_builder' => function($repo) {
return $repo->dosiersOfCurrentUser();
},
'choice_label' => 'name',
))
->add('save', SubmitType::class, array(
'label' => 'Save',
'attr'=> array('class'=>'btn btn-success submitButton')
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Line'
));
}
public function getBlockPrefix()
{
return 'appbundle_line';
}
}
My Line.php (entity)
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Line
*
* #ORM\Table(name="line")
* #ORM\Entity(repositoryClass="AppBundle\Repository\LineRepository")
*/
class Line
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Dosier", inversedBy="lines")
* #ORM\JoinColumn(name="dosier_id", referencedColumnName="id")
*/
private $dosier;
/**
* #ORM\OneToMany(targetEntity="Loan", mappedBy="line")
*/
private $loans;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
public function __construct()
{
$this->loans = new ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Line
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add loan
*
* #param \AppBundle\Entity\Loan $loan
*
* #return Line
*/
public function addLoan(\AppBundle\Entity\Loan $loan)
{
$this->loans[] = $loan;
return $this;
}
/**
* Remove loan
*
* #param \AppBundle\Entity\Loan $loan
*/
public function removeLoan(\AppBundle\Entity\Loan $loan)
{
$this->loans->removeElement($loan);
}
/**
* Get loans
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getLoans()
{
return $this->loans;
}
/**
* Set dosier
*
* #param \AppBundle\Entity\Dosier $dosier
*
* #return Line
*/
public function setDosier(\AppBundle\Entity\Dosier $dosier = null)
{
$this->dosier = $dosier;
return $this;
}
/**
* Get dosier
*
* #return \AppBundle\Entity\Dosier
*/
public function getDosier()
{
return $this->dosier;
}
}
and my repository : DosierRepository.php
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class DosierRepository extends \Doctrine\ORM\EntityRepository
{
public function dosiersOfCurrentUser() {
return $this->createQueryBuilder('dosier')
->where('dosier.userId = 1 ')
->orderBy('dosier.name', 'DESC');
}
}
How can I get the current user, or at least the current user id, to make a query like ... select from Dosier where dosier.user_id = $???
In controller where you are buiding your form you need pass the user object to your form type
$tokenStorage = $this->get('security.token_storage');
$form = $this->createForm(new LineType($tokenStorage), $line);
//... other stuff
Now in your form type recieve this tokenStorage object and retrieve user object and pass to your repo function
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
//.. other use statements
class LineType extends AbstractType
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$userId = $this->user->getId();
$builder
->add('name')->add('dosier')
->add('dosier', EntityType::class, array(
'class' => 'AppBundle:Dosier',
'query_builder' => function($repo) use($userId) {
return $repo->dosiersOfCurrentUser($userId);
},
'choice_label' => 'name',
))
->add('save', SubmitType::class, array(
'label' => 'Save',
'attr'=> array('class'=>'btn btn-success submitButton')
)
);
}
}
In repo apply your filter
class DosierRepository extends \Doctrine\ORM\EntityRepository
{
public function dosiersOfCurrentUser($userId) {
return $this->createQueryBuilder('dosier')
->where('dosier.userId = :userId ')
->setParameter('userId',$userId)
->orderBy('dosier.name', 'DESC');
}
}
I have a form for inserting an entity Category. This entity has two other entities that are related to it.
One related entity is an other separate Entity Group. The other entity is itself self-referenced Category that is an array collection that represents preconditions. So far so good, i can persist the main entity with the relations with the correct ORM annotations.
Rough scheme of Category
id : int
title: string
group : Group obj
preconditions : [Category obj, Category obj, ...]
I made an type class for creating the form as described as best-practice in the documentation.
$form = $this->createForm(new CategoryType($em));
Situation
Before i persist the entity, i must initialize it and set the posted datas to it. The posted related objects can’t simply setted to the persisting entity, because they have the wrong datatype. (E.g. the self-referencing collection is posted only as array with id’s, and not an array collection of the choosed items.)
So i catch this raw datas and get separatelly the related entities from the entity manager.
Goal
The inserting entity should be filled automatically with the related entities, whitout get those separately through the entity manager
Question
Is this the meaning of the form component that those related objects are not posted and made available fully? Or what im missing in my implementation?
Is there a way to do this more automated?
On the form class for the ‘preconditions’ property i had to do mapped => false otherwise i recieve an exception that a wrong type was passed. But at the end i want that the form matches all automatically through the mapping, whitout skipping a mapping, and whitout getting the related entities separately from the entity manager.
class CategoryType extends AbstractType
{
public function __construct($em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$qb = $this->em->createQueryBuilder();
$categories = $qb->select('e.id, e.title')
->from('MyvendorCoreBundle:Category', 'e')
->indexBy('e', 'e.id')
->orderBy('e.title')
->getQuery()
->getResult();
$categories_choice = array_map(function ($value) {
return $value['title'];
}, $categories);
$builder->add('title')
->add('group_Id', new GroupType($this->em))
->add('preconditions', 'choice', array(
'choices' => $categories_choice,
'multiple' => true,
'mapped' => false
))
->add('save', 'submit');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Myvendor\CoreBundle\Entity\Category'
));
}
public function getName()
{
return 'category';
}
}
Controller method
public function newAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new CategoryType($em));
// Repopulating the form after submission
$form->handleRequest($request);
// Prepare a new empty Category
$category = new Category();
if ($form->isValid()) {
/* Catch some raw datas posted from the form */
// Posted precondition category ids to get its entities more later
$precondition_category_ids = $form->get('preconditions')->getData();
// Posted group entity that have only filled the group id in the object
$group_raw = $form->get('group_Id')->getData();
// Get the explicit filled group entity throuth the posted id.
$group = $em->find('MyvendorCoreBundle:Group', $group_raw->getGroupid());
// Fill the prepaired group with the posted datas
$category->setTitle($form->get('title')->getData());
$category->setGroupId($group);
// Adding preconditions
try {
for ($i = 0; count($precondition_category_ids) > $i; $i ++) {
$precondition_category_id = $precondition_category_ids[$i];
if (0 >= $precondition_category_id) { // Retrieving id must be greater than 0
throw new \Exception('Error retrieving precondition id');
}
$precondition_category = $em->find('MyvendorCoreBundle:Category', $precondition_category_id);
if ($precondition_category instanceof Category) {
$category->addPrecondition($precondition_category);
} else {
throw new \Exception('Error retrieving precondition as Myvendor\CoreBundle\Entity\Category');
}
}
$em->persist($category); // Insert the group item with its relations
$em->flush();
echo '<h1 style="color:green">persisted</h1>';
} catch (\Exception $e) {
echo '<h1 style="color:red">' . $e->getMessage() . '</h1>';
}
}
return $this->render('MyvendorCoreBundle:fbm:new.html.twig', array(
'form' => $form->createView()
));
}
GroupType
class GroupType extends AbstractType
{
public function __construct($em){
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$groups = $this->em->createQuery("
SELECT o.groupid, o.descr
FROM MyvendorCoreBundle:Group o
INDEX BY o.groupid
ORDER BY o.descr
")->getResult();
$groups_dropdown = array();
$groups_dropdown = array_map(function($value) { return $value['descr']; }, $groups);
$builder->add('groupid', 'choice', array(
'label' => false,
'choices' => $groups_dropdown,
'attr' => array('style' => 'width: 300px')
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Myvendor\CoreBundle\Entity\Group',
));
}
public function getName()
{
return 'group';
}
}
/**
* #ORM\Entity
* #ORM\Table(name="category")
*/
class Category
{
public function __construct()
{
$this->preconditions = new ArrayCollection();
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var \Myvendor\CoreBundle\Entity\Group
*
* #Assert\Type(type="Myvendor\CoreBundle\Entity\Group")
* #Assert\Valid()
* #ORM\ManyToOne(targetEntity="Myvendor\CoreBundle\Entity\Group", inversedBy="Category")
* #ORM\JoinColumn(name="group_id", nullable=false, referencedColumnName="groupid")
*/
private $group_Id;
/**
* #var string
* #Assert\NotBlank()
* #ORM\Column(type="string", length=255, nullable=false)
*/
private $title;
/**
* Preconditions are Categorys referencing to an Category.
* For a single Category its empty (which have no subelements).
* A join table holds the references of a main Category to its sub-Categorys (preconditions)
*
* #ORM\ManyToMany(targetEntity="Category")
* #ORM\JoinTable(name="category_precondition",
* joinColumns={#JoinColumn(name="category_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="category_precondition_id", referencedColumnName="id")}
* )
*/
private $preconditions;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Category
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set groupId
*
* #param \Myvendor\CoreBundle\Entity\Group $groupId
*
* #return Category
*/
public function setGroupId(\Myvendor\CoreBundle\Entity\Group $groupId)
{
$this->group_Id = $groupId;
return $this;
}
/**
* Get groupId
*
* #return \Myvendor\CoreBundle\Entity\Group
*/
public function getGroupId()
{
return $this->group_Id;
}
/**
* Add precondition
*
* #param \Myvendor\CoreBundle\Entity\Category $precondition
*
* #return $this
*/
public function addPrecondition(\Myvendor\CoreBundle\Entity\Category $precondition)
{
$this->preconditions[] = $precondition;
return $this;
}
/**
* Get preconditions
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPreconditions()
{
return $this->preconditions;
}
/**
* Group
*
* #ORM\Table(name="group", indexes={#ORM\Index(name="homepage", columns={"homepage"}), #ORM\Index(name="theme", columns={"theme"})})
* #ORM\Entity
*/
class Group
{
/**
* #var string
*
* #ORM\Column(name="descr", type="string", length=60, nullable=true)
*/
private $descr;
/**
* #var integer
*
* #Assert\NotBlank()
* #ORM\Column(name="groupid", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
public $groupid;
/**
* Set descr
*
* #param string $descr
* #return Group
*/
public function setDescr($descr)
{
$this->descr = $descr;
return $this;
}
/**
* Get descr
*
* #return string
*/
public function getDescr()
{
return $this->descr;
}
/**
* Get groupid
*
* #return integer
*/
public function getGroupid()
{
return $this->groupid;
}
}
The solution was that the type of the selecting choice entities, must be not a choicelist, but really an collection type.
So use something like this
->add('preconditions', 'collection', array(
'entry_type' => 'entity',
'entry_options' => array(
'class' => 'MyVendorCoreBundle:EduStructItem',
'choice_label' => 'title'
),
'allow_add' => true,
'allow_delete' => true
))
instead of
->add('preconditions', 'choice', array(
'choices' => $categories_choice,
'multiple' => true,
'mapped' => false
))
I've been struggling with this for a few days now, so I hope somebody can help.
I have a OneToMany relation between a table Resources and my FOSUserBundle provided User entity, so that one user can have posted many resources.
Note: the entities have been edited for brevity, however I don't believe I removed anything important for this question
My Resources entity:
<?php
namespace SFI\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="SFI\MainBundle\Entity\ResourcesRepository")
* #ORM\Table(name="resources")
*/
class Resources {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id()
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
/**
* #ORM\Column(type="string", length=255)
*/
protected $type;
/**
* #ORM\Column(type="string", length=255)
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $description;
/**
* #ORM\Column(type="datetime")
*/
protected $created_time;
/**
* #ORM\ManyToOne(targetEntity="SFI\UserBundle\Entity\User", inversedBy="created_by")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
*/
protected $created_by;
---rest of getters and setters---
/**
* Set created_by
*
* #param \SFI\UserBundle\Entity\User $createdBy
* #return Resources
*/
public function setCreatedBy(\SFI\UserBundle\Entity\User $createdBy = null)
{
$this->created_by = $createdBy;
return $this;
}
/**
* Get created_by
*
* #return \SFI\UserBundle\Entity\User
*/
public function getCreatedBy()
{
return $this->created_by;
}
}
My User entity:
<?php
namespace SFI\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="SFI\UserBundle\Entity\UserRepository")
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
* #Assert\Length(
* min=3,
* max="255",
* minMessage="The name is too short.",
* maxMessage="The name is too long.",
* groups={"Registration", "Profile"}
* )
*/
protected $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return User
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #ORM\OneToMany(targetEntity="SFI\MainBundle\Entity\Resources", mappedBy="created_by")
*/
protected $created_by;
/**
* Add created_by
*
* #param \SFI\MainBundle\Entity\Resources $createdBy
* #return User
*/
public function addCreatedBy(\SFI\MainBundle\Entity\Resources $createdBy)
{
$this->created_by[] = $createdBy;
return $this;
}
/**
* Remove created_by
*
* #param \SFI\MainBundle\Entity\Resources $createdBy
*/
public function removeCreatedBy(\SFI\MainBundle\Entity\Resources $createdBy)
{
$this->created_by->removeElement($createdBy);
}
/**
* Get created_by
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCreatedBy()
{
return $this->created_by;
}
}
I have a couple of Form Types but the main one I'm working with is ResourceType:
<?php
namespace SFI\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ResourceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//Setting date and time
$created_at = new \DateTime('now');
$builder
->add('name', 'text', array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Resource name',
),
))
->add('type', 'choice', array(
'required' => true,
'empty_value' => 'Choose a type',
'choices' => array('w' => 'Website', 'v' => 'Video', 'a' => 'Audio'),
'attr' => array(
'class' => 'form-control',
),
))
->add('link', 'text', array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Add a link',
),
))
->add('description', 'textarea', array(
'required' => true,
'attr' => array(
'class' => 'textarea',
'style' => 'width: 100%; height: 200px; font-size: 14px; line-height: 18px; border: 1px solid #dddddd; padding: 10px;',
'placeholder' => 'Write a description...',
),
))
->add('created_time', 'datetime', array(
'disabled' => true,
'data' => $created_at,
))
->add('save', 'submit', array(
'attr' => array('class' => 'btn btn-primary'),
));
}
public function getName()
{
return 'resource';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SFI\MainBundle\Entity\Resources',
));
}
}
My controller for the Resource form part:
public function resourcesAction(Request $request)
{
$breadcrumbs = $this->get("white_october_breadcrumbs");
$breadcrumbs->addItem("SFI Portalen", $this->get("router")->generate("sfi_static_index"));
$breadcrumbs->addItem("Teacher Dashboard", $this->get("router")->generate("sfi_teacher_dashboard"));
$breadcrumbs->addItem("Resources");
$em = $this->getDoctrine()->getManager();
$resource = new Resources();
$newResourceForm = $this->createForm(new ResourceType(), $resource);
$newResourceForm->handleRequest($request);
if ($newResourceForm->isValid()) {
$session = $this->getRequest()->getSession();
//$session->getFlashBag()->add('notice', 'Form processed');
$session->getFlashBag()->add('notice', 'Form processed, testing, no persist');
//$em->persist($resource);
//$em->flush();
return $this->redirect($this->generateUrl('sfi_teacher_resources'));
}
return $this->render('SFIMainBundle:Teacher:resources.html.twig', array(
'form' => $newResourceForm->createView(),
));
}
What I need to do is relate these entities when a new Resource is created, to "assign" it to the logged in user via the relation in the tables.
I've looked into embedded forms and other suggestions on Symfony's documentation, but I can't wrap my head around how to apply what I read to this specific example. I have followed the example with the tasks and the tags and the categories and all that, but I can't understand how it applies to my situation, having the User entity in another bundle, and a User form type that relates the user to their school and just adds an extra full name field, while inheriting FOSUserBundle's original form type.
I also have a funny feeling that I haven't tackled or "layed out" the relationship correctly in my code so as to make it easier to understand which objects relate.
Any help would be much appreciated! Also please let me know if you require other code or further information.
You should just be able to set the user manually before persisting the Resources entity.
if ($newResourceForm->isValid()) {
...
$resource->setCreatedBy($this->getUser());
$em->persist($resource);
$em->flush();
...
}
Symfony makes use of the $this->getUser() shortcut in controllers (see http://symfony.com/doc/current/book/security.html#retrieving-the-user-object). I'm assuming you only allow this form when the user is logged in.
Also if you are using Doctrine I would recommend using it to generate your entities and getters/setters for you, as it will generate sensible names that coincide with Symfony conventions. For instance, your entity would end up being named 'Resource' vs. 'Resources', and your variables would be camelCased ($createdBy vs. $created_by).
OK so based on Jason's suggestion, and the previous one by sjagr, I have renamed a few things for consistency and better understand, and I have written the following, which works correctly!
Thank you all for your help!
public function resourcesAction(Request $request)
{
$breadcrumbs = $this->get("white_october_breadcrumbs");
$breadcrumbs->addItem("SFI Portalen", $this->get("router")->generate("sfi_static_index"));
$breadcrumbs->addItem("Teacher Dashboard", $this->get("router")->generate("sfi_teacher_dashboard"));
$breadcrumbs->addItem("Resources");
$em = $this->getDoctrine()->getManager();
$resource = new Resource();
$createdBy = $em->getRepository('SFIUserBundle:User')->find($this->getUser()->getId());
$newResourceForm = $this->createForm(new ResourceType(), $resource);
$newResourceForm->handleRequest($request);
if ($newResourceForm->isValid()) {
//Getting current date and time
$created_time = new \DateTime('now');
$resource->setCreatedTime($created_time);
$resource->setCreatedBy($createdBy);
$session = $this->getRequest()->getSession();
$session->getFlashBag()->add('notice', 'Form processed');
$em->persist($resource);
$em->flush();
return $this->redirect($this->generateUrl('sfi_teacher_resources'));
}
return $this->render('SFIMainBundle:Teacher:resources.html.twig', array(
'form' => $newResourceForm->createView(),
));
}
All I needed was to pass the current user's entire object in setCreatedBy and the relation works correctly.
Thank you all again!
Now I have problem with submitting post data in my form (my forms looks like:
Task: <input text>
Category: <multiple select category>
DueDate: <date>
<submit>
)
And after submitting my form, I'll get this error:
Found entity of type Doctrine\Common\Collections\ArrayCollection on association Acme\TaskBundle\Entity\Task#category, but expecting Acme\TaskBundle\Entity\Category
My sources:
Task Object Task.php
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="tasks")
*/
class Task
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=200)
* #Assert\NotBlank(
* message = "Task cannot be empty"
* )
* #Assert\Length(
* min = "3",
* minMessage = "Task is too short"
* )
*/
protected $task;
/**
* #ORM\Column(type="datetime")
* #Assert\NotBlank()
* #Assert\Type("\DateTime")
*/
protected $dueDate;
/**
* #Assert\True(message = "You have to agree")
*/
protected $accepted;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
*/
protected $category;
/**
* Constructor
*/
public function __construct()
{
$this->category = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set task
*
* #param string $task
* #return Task
*/
public function setTask($task)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* #return string
*/
public function getTask()
{
return $this->task;
}
/**
* Set dueDate
*
* #param \DateTime $dueDate
* #return Task
*/
public function setDueDate($dueDate)
{
$this->dueDate = $dueDate;
return $this;
}
/**
* Get dueDate
*
* #return \DateTime
*/
public function getDueDate()
{
return $this->dueDate;
}
/**
* Add category
*
* #param \Acme\TaskBundle\Entity\Category $category
* #return Task
*/
public function addCategory(\Acme\TaskBundle\Entity\Category $category)
{
$this->category[] = $category;
return $this;
}
/**
* Remove category
*
* #param \Acme\TaskBundle\Entity\Category $category
*/
public function removeCategory(\Acme\TaskBundle\Entity\Category $category)
{
$this->category->removeElement($category);
}
/**
* Get category
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCategory()
{
return $this->category;
}
}
Category Object Category.php
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=200, unique=true)
* #Assert\NotNull(message="Choose a category", groups = {"adding"})
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="Task", mappedBy="category")
*/
private $tasks;
public function __toString()
{
return strval($this->name);
}
/**
* Constructor
*/
public function __construct()
{
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
* #return Category
*/
public function addTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks[] = $tasks;
return $this;
}
/**
* Remove tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
*/
public function removeTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks->removeElement($tasks);
}
/**
* Get tasks
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTasks()
{
return $this->tasks;
}
}
TaskType TaskType.php
<?php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Acme\TaskBundle\Form\Type\Category;
class TaskType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
'cascade_validation' => true,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task', 'text', array('label' => 'Task'))
->add('dueDate', 'date', array('label' => 'Due Date'))
->add('category', new CategoryType(), array('validation_groups' => array('adding')))
//->add('accepted', 'checkbox')
->add('save', 'submit', array('label' => 'Submit'));
}
public function getName()
{
return 'task';
}
}
CategoryType CategoryType.php
<?php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CategoryType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'entity', array(
'class' => 'AcmeTaskBundle:Category',
'query_builder' => function($repository) { return $repository->createQueryBuilder('c')->orderBy('c.id', 'ASC'); },
'property' => 'name',
'multiple' => true,
'label' => 'Categories',
));
}
public function getName()
{
return 'category';
}
}
and my Controller DefaultController.php:
public function newAction(Request $request)
{
$task = new Task();
$task->setTask('Write name here...');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createForm('task', $task);
$form->handleRequest($request);
if($form->isValid())
{
$this->get('session')->getFlashBag()->add(
'success',
'Task was successfuly created'
);
$em = $this->getDoctrine()->getManager();
/*
$category = $this->getDoctrine()->getManager()->getRepository('AcmeTaskBundle:Category')->findOneByName($form->get('category')->getData());
$task->setCategory($category);
*/
$em->persist($task);
try {
$em->flush();
} catch (\PDOException $e) {
// sth
}
//$nextAction = $form->get('saveAndAdd')->isClicked() ? 'task_new' : 'task_success';
//return $this->redirect($this->generateUrl($nextAction));
}
return $this->render('AcmeTaskBundle:Default:new.html.twig', array('form' => $form->createView()));
}
So, I looked at this problem at the google, but there were different kinds of problems. Any idea?
UPDATE
Full error message:
[2013-09-30 14:43:55] request.CRITICAL: Uncaught PHP Exception Doctrine\ORM\ORMException: "Found entity of type Doctrine\Common\Collections\ArrayCollection on association Acme\TaskBundle\Entity\Task#category, but expecting Acme\TaskBundle\Entity\Category" at /var/www/Symfony/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 762 {"exception":"[object] (Doctrine\\ORM\\ORMException: Found entity of type Doctrine\\Common\\Collections\\ArrayCollection on association Acme\\TaskBundle\\Entity\\Task#category, but expecting Acme\\TaskBundle\\Entity\\Category at /var/www/Symfony/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:762)"} []
UPDATE 2
My TWIG template new.html.twig
<html>
<head>
<title>Task create</title>
</head>
<body>
{% for flashMessage in app.session.flashbag.get('success') %}
<div style="display: block; padding: 15px; border: 1px solid green; margin: 15px; width: 450px;">
{{ flashMessage }}
</div>
{% endfor %}
{{ form_start(form, {'action': path ('task_new'), 'method': 'POST', 'attr': {'novalidate': 'novalidate' }}) }}
{{ form_errors(form) }}
<div>
{{ form_label(form.task) }}:<br>
{{ form_widget(form.task) }} {{ form_errors(form.task) }}<br>
</div>
<div>
{{ form_label(form.category) }}:<br>
{{ form_widget(form.category) }} {{ form_errors(form.category) }}
</div>
<div>
{{ form_label(form.dueDate) }}:<br>
{{ form_widget(form.dueDate) }} {{ form_errors(form.dueDate) }}<br>
</div>
{{ form_end(form) }}
</body>
</html>
You can improve the controller code by changing your setters in the entities:
In Task:
public function addCategory(Category $category)
{
if (!$this->categories->contains($category)) {
$this->categories->add($category);
$category->addTask($this); // Fix this
}
}
And in Category:
public function addTask(Task $task)
{
if (!this->tasks->contains($task)) {
$this->tasks->add($task);
$task->addCategory($this);
}
}
This will keep the elements in the ArrayCollection unique so you won't have to do that checking in your code and will also set the inverse side automatically.
So I found my solution! This code works:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task', 'text', array('label' => 'Task'))
->add('dueDate', 'date', array('label' => 'Date', 'format' => 'ddMMMMyyyy'))
->add('category', 'entity', array('required' => true, 'multiple' => true, 'class' => 'AcmeTaskBundle:Category', 'query_builder' => function($repository) { return $repository->createQueryBuilder('c')->orderBy('c.id', 'ASC'); },))
->add('save', 'submit', array('label' => 'Send'));
}
And my controller is working in this form:
if($form->isValid())
{
$this->get('session')->getFlashBag()->add(
'success',
'Task successfuly added'
);
$em = $this->getDoctrine()->getManager();
foreach($form->get('category')->getData() as $cat)
{
$task->removeCategory($cat);
$task->addCategory($cat);
}
$em->persist($task);
try {
$em->flush();
} catch (\PDOException $e) {
// sth
}
}
I'm forced to use $task->removeCategory($cat) because I'll get error of dublication primary indexes if it will not be here.
Now I'm trying to resolve my problem with embedded and non-embedded forms -> Symfony2, validation embedded and non-embedded forms with same parameters and different results?
I believe that my questions will be helpful for beginners with Symfony2 having same problems.
You are defining a manyToMany relationship from task to category, which means you can have more than one category for each task. If you are trying to have multiple categories in a task, you can try changing this line
->add('category', new CategoryType(), array('validation_groups' => array('adding')))
for something like
->add('category', 'collection', array('type' => new CategoryType()))
check symfony's documentation on form collection and this cookbook
I am not sure, but try to add a setCategory method to your Task entity, because currently you only have the add function and thats why the form component is calling the addFunction instead of the set for the whole arraycollection.
I think problem in your form.
Your task have ManyToMany relation with categotry and stored in field category(logical it is a categories). But in the form you set '->add('category', new CategoryType())' which means that you add new type that is a array or something(data_class not defined) and this type contains another one field.
Solution is to directly define field in your form(without using standalone form type) Or extend category form type from entity type(by default type extended form type).
I have a M2M relationship, but when I create an item the relationship is not saved and I can't find where's the problem.
The Model:
class Serie
{
/**
* #ORM\ManyToMany(targetEntity="Magazine", mappedBy="series")
* */
protected $magazines;
/**
* Constructor
*/
public function __construct()
{
$this->magazines = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add magazines
*
* #param MyList\DBBundle\Entity\Magazine $magazines
* #return Serie
*/
public function addMagazine(\MyList\DBBundle\Entity\Magazine $magazines)
{
$this->magazines[] = $magazines;
return $this;
}
/**
* Remove magazines
*
* #param MyList\DBBundle\Entity\Magazine $magazines
*/
public function removeMagazine(\MyList\DBBundle\Entity\Magazine $magazines)
{
$this->magazines->removeElement($magazines);
}
/**
* Get magazines
*
* #return Doctrine\Common\Collections\Collection
*/
public function getMagazines()
{
return $this->magazines;
}
}
The Magazine class:
class Magazine
{
/**
* #ORM\ManyToMany(targetEntity="Serie" , inversedBy="magazines")
* #ORM\JoinTable(name="magazines_series")
* */
protected $series;
public function __construct()
{
$this->series = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add series
*
* #param MyList\DBBundle\Entity\Serie $series
* #return Magazine
*/
public function addSerie(\MyList\DBBundle\Entity\Serie $serie)
{
$serie->addMagazine($this);
$this->series[] = $serie;
return $this;
}
/**
* Remove series
*
* #param MyList\DBBundle\Entity\Serie $series
*/
public function removeSerie(\MyList\DBBundle\Entity\Serie $series)
{
$this->series->removeElement($series);
}
/**
* Get series
*
* #return Doctrine\Common\Collections\Collection
*/
public function getSeries()
{
return $this->series;
}
}
The Controller
class SerieController extends Controller
{
public function newAction()
{
$entity = new Serie();
$form = $this->createForm(new SerieType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
/**
* Creates a new Serie entity.
*
* #Route("/create", name="serie_create")
* #Method("POST")
* #Template("DBBundle:Serie:new.html.twig")
*/
public function createAction(Request $request)
{
$entity = new Serie();
$form = $this->createForm(new SerieType(), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('serie_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
}
And the Form:
class SerieType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('start')
->add('end')
->add('type')
->add('status')
->add('magazines','entity',array(
'class' => 'DBBundle:Magazine',
'multiple' => true,
'property' => 'name'
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyList\DBBundle\Entity\Serie'
));
}
public function getName()
{
return 'mylist_dbbundle_serietype';
}
}
I know there are some questions about this (i.e. Symfony2-Doctrine: ManyToMany relation is not saved to database), but none of them have solved my problem.
This is because you never persist() the magazine, the form added it already to the entity but Doctrine still needs to manage it.
add this in the if ($form->isValid()):
...
$magazines = $entity->getMagazines();
foreach($magazines as $magazine){
$em->persist($magazine);
}
...
$em->flush();