Symfony FOS Rest Bundle Api Call Many to Many Relationship - php

I seem to have an issue. And I'm sure it has a really simple fix. I have an API which uses FOS Rest bundle. I have a call which sends a POST request to postCreateTimesheetAction( Request $request ) which creates an empty row in the Timesheet Table. I have another call patchTimesheetAction which laters adds some data to the table. Okay all is well. I have another call patchCareoptionAction( Timesheet $timesheet ) which creates a row in a table which has a many to many relationship built from CareOptions.php(below) and Timesheet.php(below). All the api calls work good and function as they should. I will post them below here:
creates a blank new timesheet row in the Timesheet table: (Timesheet.php)
curl -H "Accept: application/json" -H "Content-type: application/json" -i -X POST -d '{"homecare_homecarebundle_timesheet":""}' www.hc-timesheets-demo.com/app_dev.php/api/v1/create/timesheet
updates the timesheet row with data:
curl -i -H "Accept: application/json" -H "Content-type: application/json" -d '{"homecare_homecarebundle_timesheet":{"agency":"157","recipient":"154","pca":"16","service":"6"}}' -X PATCH www.hc-timesheets-demo.com/app_dev.php/api/v1/timesheet/31
and finally creates the many to many relationship:
curl -i -H "Accept: application/json" -H "Content-type: application/json" -d '{"homecare_homecarebundle_timesheet":{"careOption":[456,457] }}' -X PATCH www.hc-timesheets-demo.com/app_dev.php/api/v1/careoption/31
notice the 31 at the end of the patch request. That is the id of the timesheet created in the table from the first api call. So lets get to my question! Whenever i call the 3rd api call i successfully create the many to many rows in the table. But when i call it again it does not replace the old rows with the new ones. It just adds more rows to the table. I want the many to many table rows to be updated and the old ones gone. Do i need to delete them out first? Let me explain a little more. If you see in the 3rd api call I'm adding 2 careOptions to the timesheet: (careOptions 456 and 457). Well if I call it again, and lets say I want to add 458 and 459. I want 456 and 457 to automatically delete and be gone. Please someone help me out??!!
Here is the owning side of the many to many relationship
<?php
//src/Homecare/HomecareBundle/Entity/CareOptions.php
namespace Homecare\HomecareBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* CareGoals
*
* #ORM\Table(name="careoptions")
* #ORM\Entity(repositoryClass="Homecare\HomecareBundle\Entity\Repository\CareOptionsRepository")
*/
class CareOptions
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="CareOptionsTimesheets", mappedBy="careOption")
*/
private $care_options_timesheets;
/**
* #var string
*
* #ORM\Column(name="care_option", type="string", length=255)
*/
private $careOption;
/**
* #ORM\ManyToMany(targetEntity="CareGoals", mappedBy="careOption")
*/
private $careGoals;
/**
* #ORM\ManyToMany(targetEntity="Timesheet", inversedBy="careOption", cascade={"persist"})
*/
private $timesheet;
public function __construct()
{
$this->care_options_timesheets = new ArrayCollection();
$this->timesheet = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
//add a to string method so that the object can be displayed in the twig template
/*
public function __toString()
{
return $this->getCareGoal();
}
*/
/**
* Set careOption
*
* #param string $careOption
* #return CareOptions
*/
public function setCareOption($careOption)
{
$this->careOption = $careOption;
return $this;
}
/**
* Get careOption
*
* #return string
*/
public function getCareOption()
{
return $this->careOption;
}
/**
* Add care_options_timesheets
*
* #param \Homecare\HomecareBundle\Entity\CareOptions_Timesheets $careOptionsTimesheets
* #return CareOptions
*/
public function addCareOptionsTimesheet(\Homecare\HomecareBundle\Entity\CareOptionsTimesheets $careOptionsTimesheets)
{
$this->care_options_timesheets[] = $careOptionsTimesheets;
return $this;
}
/**
* Remove care_options_timesheets
*
* #param \Homecare\HomecareBundle\Entity\CareOptions_Timesheets $careOptionsTimesheets
*/
public function removeCareOptionsTimesheet(\Homecare\HomecareBundle\Entity\CareOptionsTimesheets $careOptionsTimesheets)
{
$this->care_options_timesheets->removeElement($careOptionsTimesheets);
}
/**
* Get care_options_timesheets
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCareOptionsTimesheets()
{
return $this->care_options_timesheets;
}
/**
* Add careGoals
*
* #param \Homecare\HomecareBundle\Entity\CareGoals $careGoals
* #return CareOptions
*/
public function addCareGoal(\Homecare\HomecareBundle\Entity\CareGoals $careGoals)
{
$this->careGoals[] = $careGoals;
return $this;
}
/**
* Remove careGoals
*
* #param \Homecare\HomecareBundle\Entity\CareGoals $careGoals
*/
public function removeCareGoal(\Homecare\HomecareBundle\Entity\CareGoals $careGoals)
{
$this->careGoals->removeElement($careGoals);
}
/**
* Get careGoals
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCareGoals()
{
return $this->careGoals;
}
public function __toString() {
return $this->getCareOption();
}
/**
* Add timesheet
*
* #param \Homecare\HomecareBundle\Entity\Timesheet $timesheet
* #return CareOptions
*/
public function addTimesheet(\Homecare\HomecareBundle\Entity\Timesheet $timesheet)
{
$this->timesheet[] = $timesheet;
return $this;
}
/**
* Remove timesheet
*
* #param \Homecare\HomecareBundle\Entity\Timesheet $timesheet
*/
public function removeTimesheet(\Homecare\HomecareBundle\Entity\Timesheet $timesheet)
{
$this->timesheet->removeElement($timesheet);
}
/**
* Get timesheet
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTimesheet()
{
return $this->timesheet;
}
}
Here is the other side of the many to many relationship:
<?php
//src/Homecare/HomecareBundle/Entity/Timesheet.php
namespace Homecare\HomecareBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\SerializedName;
/**
* Timesheet
*
* #ORM\Table(name="timesheet")
* #ORM\Entity(repositoryClass="Homecare\HomecareBundle\Entity\Repository\TimesheetRepository")
*/
class Timesheet
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Type("integer")
* #SerializedName("id")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Agency", inversedBy="actualTimesheets")
* #ORM\JoinColumn(name="agency_id", referencedColumnName="id")
* #Type("Homecare\HomecareBundle\Entity\Agency")
* #SerializedName("agency")
*/
private $agency;
/**
* #ORM\ManyToOne(targetEntity="Recipient", inversedBy="actualTimesheets")
* #ORM\JoinColumn(name="recipient_id", referencedColumnName="id")
* #Type("Homecare\HomecareBundle\Entity\Recipient")
* #SerializedName("recipient")
*/
private $recipient;
/**
* #ORM\ManyToOne(targetEntity="Pca", inversedBy="actualTimesheets")
* #ORM\JoinColumn(name="pca_id", referencedColumnName="id")
* #Type("Homecare\HomecareBundle\Entity\Pca")
* #SerializedName("pca")
*/
private $pca;
/**
* #ORM\ManyToOne(targetEntity="Services", inversedBy="actualTimesheets")
* #ORM\JoinColumn(name="service_id", referencedColumnName="id")
* #Type("Homecare\HomecareBundle\Entity\Services")
* #SerializedName("service")
*/
private $service;
/**
* #ORM\Column(name="continueTimesheetNumber",type="integer",nullable=true)
* #Type("integer")
* #SerializedName("continueTimesheetNumber")
*/
private $continueTimesheetNumber;
/**
* #ORM\ManyToOne(targetEntity="VisitRatios", inversedBy="timesheets")
* #Type("Homecare\HomecareBundle\Entity\VisitRatios")
* #SerializedName("visitRatio")
*/
private $visitRatio;
/**
* #ORM\ManyToOne(targetEntity="TimeIn", inversedBy="timesheets")
* #Type("Homecare\HomecareBundle\Entity\TimeIn")
* #SerializedName("timeIn")
*/
private $timeIn;
/**
* #ORM\ManyToOne(targetEntity="TimeOut", inversedBy="timesheets")
* #Type("Homecare\HomecareBundle\Entity\TimeOut")
* #SerializedName("timeOut")
*/
private $timeOut;
/**
* #ORM\ManyToOne(targetEntity="Files", inversedBy="timesheets")
* #Type("Homecare\HomecareBundle\Entity\Files")
* #SerializedName("file")
*/
private $file;
/**
* #ORM\ManyToMany(targetEntity="CareOptions", mappedBy="timesheet", cascade={"persist"})
* #Type("Homecare\HomecareBundle\Entity\CareOptions")
* #SerializedName("careOption")
*/
private $careOption;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set agency
*
* #param \Homecare\HomecareBundle\Entity\Agency $agency
* #return Timesheet
*/
public function setAgency(\Homecare\HomecareBundle\Entity\Agency $agency = null)
{
$this->agency = $agency;
return $this;
}
/**
* Get agency
*
* #return \Homecare\HomecareBundle\Entity\Agency
*/
public function getAgency()
{
return $this->agency;
}
/**
* Set recipient
*
* #param \Homecare\HomecareBundle\Entity\Recipient $recipient
* #return Timesheet
*/
public function setRecipient(\Homecare\HomecareBundle\Entity\Recipient $recipient = null)
{
$this->recipient = $recipient;
return $this;
}
/**
* Get recipient
*
* #return \Homecare\HomecareBundle\Entity\Recipient
*/
public function getRecipient()
{
return $this->recipient;
}
/**
* Set pca
*
* #param \Homecare\HomecareBundle\Entity\Pca $pca
* #return Timesheet
*/
public function setPca(\Homecare\HomecareBundle\Entity\Pca $pca = null)
{
$this->pca = $pca;
return $this;
}
/**
* Get pca
*
* #return \Homecare\HomecareBundle\Entity\Pca
*/
public function getPca()
{
return $this->pca;
}
/**
* Set service
*
* #param \Homecare\HomecareBundle\Entity\Services $service
* #return Timesheet
*/
public function setService(\Homecare\HomecareBundle\Entity\Services $service = null)
{
$this->service = $service;
return $this;
}
/**
* Get service
*
* #return \Homecare\HomecareBundle\Entity\Services
*/
public function getService()
{
return $this->service;
}
/**
* Set continueTimesheetNumber
*
* #param integer $continueTimesheetNumber
* #return Timesheet
*/
public function setContinueTimesheetNumber($continueTimesheetNumber)
{
$this->continueTimesheetNumber = $continueTimesheetNumber;
return $this;
}
/**
* Get continueTimesheetNumber
*
* #return integer
*/
public function getContinueTimesheetNumber()
{
return $this->continueTimesheetNumber;
}
/**
* Set visitRatio
*
* #param \Homecare\HomecareBundle\Entity\VisitRatios $visitRatio
* #return Timesheet
*/
public function setVisitRatio(\Homecare\HomecareBundle\Entity\VisitRatios $visitRatio = null)
{
$this->visitRatio = $visitRatio;
return $this;
}
/**
* Get visitRatio
*
* #return \Homecare\HomecareBundle\Entity\VisitRatios
*/
public function getVisitRatio()
{
return $this->visitRatio;
}
/**
* Set timeIn
*
* #param \Homecare\HomecareBundle\Entity\TimeIn $timeIn
* #return Timesheet
*/
public function setTimeIn(\Homecare\HomecareBundle\Entity\TimeIn $timeIn = null)
{
$this->timeIn = $timeIn;
return $this;
}
/**
* Get timeIn
*
* #return \Homecare\HomecareBundle\Entity\TimeIn
*/
public function getTimeIn()
{
return $this->timeIn;
}
/**
* Set timeOut
*
* #param \Homecare\HomecareBundle\Entity\TimeOut $timeOut
* #return Timesheet
*/
public function setTimeOut(\Homecare\HomecareBundle\Entity\TimeOut $timeOut = null)
{
$this->timeOut = $timeOut;
return $this;
}
/**
* Get timeOut
*
* #return \Homecare\HomecareBundle\Entity\TimeOut
*/
public function getTimeOut()
{
return $this->timeOut;
}
/**
* Set file
*
* #param \Homecare\HomecareBundle\Entity\Files $file
* #return Timesheet
*/
public function setFile(\Homecare\HomecareBundle\Entity\Files $file = null)
{
$this->file = $file;
return $this;
}
/**
* Get file
*
* #return \Homecare\HomecareBundle\Entity\Files
*/
public function getFile()
{
return $this->file;
}
/**
* Constructor
*/
public function __construct()
{
$this->careOption = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add careOption
*
* #param \Homecare\HomecareBundle\Entity\CareOptions $careOption
* #return Timesheet
*/
public function addCareOption(\Homecare\HomecareBundle\Entity\CareOptions $careOption)
{
$careOption->addTimesheet( $this );
$this->careOption[] = $careOption;
return $this;
}
/**
* Remove careOption
*
* #param \Homecare\HomecareBundle\Entity\CareOptions $careOption
*/
public function removeCareOption(\Homecare\HomecareBundle\Entity\CareOptions $careOption)
{
$this->careOption->removeElement($careOption);
}
/**
* Get careOption
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCareOption()
{
return $this->careOption;
}
}
Here is the formBuilder TimesheetType.php
<?php
//src/Homecare/HomecareBundle/Form/TimesheetType.php
namespace Homecare\HomecareBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TimesheetType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('agency')
->add('recipient')
->add('pca')
->add('service')
->add('continueTimesheetNumber')
->add('visitRatio')
->add('timeIn')
->add('timeOut')
;
$builder->add('careOption', 'entity', array(
'class' => 'HomecareHomecareBundle:CareOptions',
'property' => 'careOption',
'expanded' => true,
'multiple' => true,
'label' => false,
'by_reference' => false,
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Homecare\HomecareBundle\Entity\Timesheet',
'csrf_protection' => false,
));
}
/**
* #return string
*/
public function getName()
{
return 'homecare_homecarebundle_timesheet';
}
}
Here are the ApiControllers:
<?php
//src/Homecare/HomecareApiBundle/Controller/TimesheetApiController.php
namespace Homecare\HomecareApiBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Controller\Annotations\View;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Homecare\HomecareBundle\Entity\Timesheet;
use Homecare\HomecareBundle\Entity\Services;
use Homecare\HomecareBundle\Entity\CareOptions;
use Homecare\HomecareBundle\Form\TimesheetType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use JMS\Serializer\SerializerBuilder;
use FOS\RestBundle\View\View as V;
use Doctrine\Common\Collections\ArrayCollection;
class TimesheetApiController extends Controller
{
/**
* #View()
*/
public function patchCareoptionAction( Timesheet $timesheet )
{
return $this->updateTimesheetForm( $timesheet );
}
/**
* #View()
*/
public function postCreateTimesheetAction( Request $request )
{
return $this->createTimesheetForm( $request, new Timesheet() );
}
/**
* #View()
*/
public function patchTimesheetAction( Timesheet $timesheet )
{
return $this->updateTimesheetForm( $timesheet );
}
private function setMethod( $statusCode, $timesheet = null ){
switch( $statusCode ) {
case 201:
return array( 'method' => 'POST' );
break;
case 204:
return array(
'action' => $this->generateUrl('patch_timesheet', array('timesheet' => $timesheet->getId())),
'method' => 'PATCH'
);
break;
}
}
/**
* #View()
*/
private function updateTimesheetForm( Timesheet $timesheet)
{
$statusCode = 204;
$form = $this->createForm(new TimesheetType(), $timesheet, $this->setMethod( $statusCode, $timesheet ) );
$form->handleRequest( $this->getRequest() );
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
//$careOption = $em->getRepository( "HomecareHomecareBundle:CareOptions" )->findOneById(456);
//$timesheet->addCareOption( $careOption );
$em->persist( $timesheet );
$em->flush();
$response = new Response();
$response->setStatusCode( $statusCode );
return $response;
}
return V::create($form, 400);
}
/**
* #View()
*/
private function createTimesheetForm( $request, Timesheet $timesheet )
{
$statusCode = 201;
$form = $this->createForm( new TimesheetType(), $timesheet, $this->setMethod( $statusCode ) );
$form->handleRequest( $this->getRequest() );
if ( $form->isValid() ) {
//$user->save();
//$timesheet = $form->getData();
$response = new Response();
$response->setStatusCode(201);
$em = $this->getDoctrine()->getManager();
$em->persist( $timesheet );
$em->flush();
//return V::create($form, 201);
//return $response;
//return array( $form, array("timesheet" => array("id" => $timesheet->getId() ) ) );
return V::create( array("createdTimesheet" => array("id" => $timesheet->getId() ) ), $statusCode );
// return array( $form, array("timesheet" => array("id" => $timesheet->getId() ) ) );
}
return V::create($form, 400);
}
}

You need to either call a second API to delete the 2 previous added options, or in your updateTimesheet you delete the 2 previous added options, you can select them by a query by creating a function in your Repository :
public function getLastTwoCareOptions(){
$query = "SELECT co.id FROM CareOptionTable co INNER JOIN TimesheetTable ts WHERE ts.id = timesheetid ORDER BY co.id LIMIT 2;";
$connection = $this->getEntityManager()->getConnection();
$prep_query = $connection->prepare($req);
$result = $prep_query->fetchAll();
}
You can make the limit as parameter to the function if you may need to update 3 last care options someday or more.
Then you get the ids and remove them from the timesheet.
Another solution is to use the same query differently to delete them with SQL query without having to select and then delete.

Related

New doctrime ORM Table field creater but it says it's still not mapped

i have added a new field/column to my "listing" entity, which is a boolean named "promoted" which is like one already present in the db "certified", it is created to behave exactly the same, however, it is'nt working..
The mariadb db updated correctly with the new row containing a boolean, but when i try to use it in a partial query or to update the field from 0 to 1 with a form it dont update, like it's unmapped, here's my code (not all just the important parts are it is 3 functions with 1000~+ lines: BaseListing.php
/**
* Listing
*
* #CocoricoAssert\ListingConstraint()
*
* #ORM\MappedSuperclass
*/
abstract class BaseListing
{
protected $price = 0;
/**
*
* #ORM\Column(name="certified", type="boolean", nullable=true)
*
* #var boolean
*/
protected $certified;
/**
*
* #ORM\Column(name="min_duration", type="smallint", nullable=true)
*
* #var integer
*/
/**
*
* #ORM\Column(name="promoted", type="boolean", nullable=true)
*
* #var boolean
*/
protected $promoted;
/**
* #return boolean
*/
public function isCertified()
{
return $this->certified;
}
/**
* #param boolean $certified
*/
public function setCertified($certified)
{
$this->certified = $certified;
}
/**
* #return boolean
*/
public function isPromoted()
{
return $this->promoted;
}
/**
* #param boolean $promoted
*/
public function setPromoted($promoted)
{
$this->promoted = $promoted;
}
}
here's AdminListing.php to generate the form for the admin panel to pass certified on / off and as i wish promoted on / off (cuted because it's a too large file):
class ListingAdmin extends AbstractAdmin
{
/** #inheritdoc */
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('admin.listing.title')
->add(
'status',
ChoiceType::class,
array(
'choices' => array_flip(Listing::$statusValues),
'placeholder' => 'admin.listing.status.label',
'translation_domain' => 'cocorico_listing',
'label' => 'admin.listing.status.label',
)
)
->add(
'adminNotation',
ChoiceType::class,
array(
'choices' => array_combine(
range(0, 10, 0.5),
array_map(
function ($num) {
return number_format($num, 1);
},
range(0, 10, 0.5)
)
),
'placeholder' => 'admin.listing.admin_notation.label',
'label' => 'admin.listing.admin_notation.label',
'required' => false,
)
)
->add(
'certified',
null,
array(
'label' => 'admin.listing.certified.label',
)
)
->add(
'promoted',
CheckboxType::class,
array(
'label' => 'admin.listing.promoted.label',
'required' => false,
)
)
This is not working because i have a query in ListingRepository that use "certified":
public function getFindSelectPart(QueryBuilder $queryBuilder)
{
$queryBuilder
->select("partial l.{id, price, averageRating, certified, createdAt, commentCount}")
->addSelect("partial t.{id, locale, slug, title, description}")
->addSelect("partial llcat.{id, listing, category}")
->addSelect("partial ca.{id, lft, lvl, rgt, root}")
->addSelect("partial cat.{id, locale, name}")
->addSelect("partial i.{id, name}")
->addSelect("partial u.{id, firstName}")
->addSelect("partial ln.{id, city, route, country}")
->addSelect("partial co.{id, lat, lng}")
->addSelect("partial ui.{id, name}")
->addSelect("'' AS DUMMY");//To maintain fields on same array level when extra fields are added
return $queryBuilder;
}
And when i put promoted just behind the certified in the partial query it says:
[Semantical Error] line 0, col 88 near partial.l Error: There is no mapped field named 'promoted' on class Cocorico\ListingBundle\Entity\Listing
besides i have done exactly the same setup as the certified value, i have cleared cache and updated DB scheme.
Here's my Entity\Listing.php :
?php
namespace Cocorico\ListingBundle\Entity;
use Cocorico\BookingBundle\Entity\Booking;
use Cocorico\ListingBundle\Model\BaseListing;
use Cocorico\ListingBundle\Model\ListingOptionInterface;
use Cocorico\ListingCategoryBundle\Entity\ListingListingCategory;
use Cocorico\ListingCharacteristicBundle\Entity\ListingListingCharacteristic;
use Cocorico\ListingDiscountBundle\Entity\ListingDiscount;
use Cocorico\ListingImageBundle\Entity\ListingImage;
use Cocorico\ListingLocationBundle\Entity\ListingLocation;
use Cocorico\MessageBundle\Entity\Thread;
use Cocorico\UserBundle\Entity\User;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Listing
*
* #ORM\Entity(repositoryClass="Cocorico\ListingBundle\Repository\ListingRepository")
*
* #ORM\Table(name="listing",indexes={
* #ORM\Index(name="created_at_l_idx", columns={"created_at"}),
* #ORM\Index(name="status_l_idx", columns={"status"}),
* #ORM\Index(name="price_idx", columns={"price"}),
* #ORM\Index(name="type_idx", columns={"type"}),
* #ORM\Index(name="min_duration_idx", columns={"min_duration"}),
* #ORM\Index(name="max_duration_idx", columns={"max_duration"}),
* #ORM\Index(name="average_rating_idx", columns={"average_rating"}),
* #ORM\Index(name="admin_notation_idx", columns={"admin_notation"}),
* #ORM\Index(name="platform_notation_idx", columns={"platform_notation"})
* })
*/
class Listing extends BaseListing
{
use ORMBehaviors\Timestampable\Timestampable;
use ORMBehaviors\Translatable\Translatable;
use \Cocorico\ListingSearchAdvancedBundle\Model\ListingSearchableTrait;
// use \Cocorico\ListingCategoryFieldBundle\Model\ListingCategoryFieldableTrait;
// use \Cocorico\DeliveryBundle\Model\ListingDeliverableTrait;
// use \Cocorico\ListingDepositBundle\Model\ListingDepositableTrait;
// use \Cocorico\ListingSessionBundle\Model\ListingSessionableTrait;
// use \Cocorico\ServiceBundle\Model\ListingTrait;
// use \Cocorico\ListingVideoBundle\Model\ListingVideoTrait;
// use \Cocorico\CarrierBundle\Model\ListingCarrierableTrait;
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Cocorico\CoreBundle\Model\CustomIdGenerator")
*
* #var integer
*/
protected $id;
/**
* #Assert\NotBlank(message="assert.not_blank")
*
* #ORM\ManyToOne(targetEntity="Cocorico\UserBundle\Entity\User", inversedBy="listings", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
*
* #var User
*/
protected $user;
/**
* #ORM\OneToOne(targetEntity="Cocorico\ListingLocationBundle\Entity\ListingLocation", inversedBy="listing", cas\
cade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="location_id", referencedColumnName="id", onDelete="CASCADE")
*
* #var ListingLocation
**/
protected $location;
/**
* #ORM\OneToMany(targetEntity="Cocorico\ListingCategoryBundle\Entity\ListingListingCategory", mappedBy="listing\
", cascade={"persist", "remove"}, orphanRemoval=true)//, fetch="EAGER"
*
*/
protected $listingListingCategories;
/**
* For Asserts #see \Cocorico\ListingBundle\Validator\Constraints\ListingValidator
*
* #ORM\OneToMany(targetEntity="Cocorico\ListingImageBundle\Entity\ListingImage", mappedBy="listing", cascade={"\
persist", "remove"}, orphanRemoval=true)
* #ORM\OrderBy({"position" = "asc"})
*/
protected $images;
/**
* #ORM\OneToMany(targetEntity="Cocorico\ListingCharacteristicBundle\Entity\ListingListingCharacteristic", mappe\
dBy="listing", cascade={"persist", "remove"}, orphanRemoval=true) //, fetch="EAGER"
*
*/
protected $listingListingCharacteristics;
/**
*
* #ORM\OneToMany(targetEntity="Cocorico\ListingDiscountBundle\Entity\ListingDiscount", mappedBy="listing", casc\
ade={"persist", "remove"}, orphanRemoval=true)
* #ORM\OrderBy({"fromQuantity" = "asc"})
*/
protected $discounts;
/**
* #ORM\OneToMany(targetEntity="Cocorico\BookingBundle\Entity\Booking", mappedBy="listing", cascade={"persist", \
"remove"}, orphanRemoval=true)
* #ORM\OrderBy({"createdAt" = "desc"})
*/
protected $bookings;
/**
* #ORM\OneToMany(targetEntity="Cocorico\MessageBundle\Entity\Thread", mappedBy="listing", cascade={"remove"}, o\
rphanRemoval=true)
* #ORM\OrderBy({"createdAt" = "desc"})
*/
protected $threads;
/**
*
* #ORM\OneToMany(targetEntity="Cocorico\ListingBundle\Model\ListingOptionInterface", mappedBy="listing", cascad\
e={"persist", "remove"}, orphanRemoval=true)
*/
protected $options;
public function __construct()
{
$this->images = new ArrayCollection();
$this->listingListingCharacteristics = new ArrayCollection();
$this->listingListingCategories = new ArrayCollection();
$this->discounts = new ArrayCollection();
$this->bookings = new ArrayCollection();
$this->threads = new ArrayCollection();
$this->options = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add characteristics
*
* #param ListingListingCharacteristic $listingListingCharacteristic
* #return Listing
*/
public function addListingListingCharacteristic(ListingListingCharacteristic $listingListingCharacteristic)
{
$this->listingListingCharacteristics[] = $listingListingCharacteristic;
return $this;
}
/**
* Remove characteristics
*
* #param ListingListingCharacteristic $listingListingCharacteristic
*/
public function removeListingListingCharacteristic(ListingListingCharacteristic $listingListingCharacteristic)
{
$this->listingListingCharacteristics->removeElement($listingListingCharacteristic);
$listingListingCharacteristic->setListing(null);
}
/**
* Get characteristics
*
* #return \Doctrine\Common\Collections\Collection|ListingListingCharacteristic[]
*/
public function getListingListingCharacteristics()
{
return $this->listingListingCharacteristics;
}
/**
* Get characteristics ordered by Group and Characteristic
*
* #return ArrayCollection
*/
public function getListingListingCharacteristicsOrderedByGroup()
{
$iterator = $this->listingListingCharacteristics->getIterator();
$iterator->uasort(
function ($a, $b) {
/**
* #var ListingListingCharacteristic $a
* #var ListingListingCharacteristic $b
*/
$groupPosA = $a->getListingCharacteristic()->getListingCharacteristicGroup()->getPosition();
$groupPosB = $b->getListingCharacteristic()->getListingCharacteristicGroup()->getPosition();
$characteristicPosA = $a->getListingCharacteristic()->getPosition();
$characteristicPosB = $b->getListingCharacteristic()->getPosition();
if ($groupPosA == $groupPosB) {
if ($characteristicPosA == $characteristicPosB) {
return 0;
}
return ($characteristicPosA < $characteristicPosB) ? -1 : 1;
}
return ($groupPosA < $groupPosB) ? -1 : 1;
}
);
return new ArrayCollection(iterator_to_array($iterator));
}
/**
* Add characteristics
*
* #param ListingListingCharacteristic $listingListingCharacteristic
* #return Listing
*/
public function addListingListingCharacteristicsOrderedByGroup(
ListingListingCharacteristic $listingListingCharacteristic
) {
return $this->addListingListingCharacteristic($listingListingCharacteristic);
}
/**
* Remove characteristics
*
* #param ListingListingCharacteristic $listingListingCharacteristic
*/
public function removeListingListingCharacteristicsOrderedByGroup(
ListingListingCharacteristic $listingListingCharacteristic
) {
$this->removeListingListingCharacteristic($listingListingCharacteristic);
}
/**
* Add category
*
* #param ListingListingCategory $listingListingCategory
* #return Listing
*/
public function addListingListingCategory(ListingListingCategory $listingListingCategory)
{
$listingListingCategory->setListing($this);
$this->listingListingCategories[] = $listingListingCategory;
return $this;
}
/**
* Remove category
*
* #param ListingListingCategory $listingListingCategory
*/
public function removeListingListingCategory(ListingListingCategory $listingListingCategory)
{
// foreach ($listingListingCategory->getValues() as $value) {
// $listingListingCategory->removeValue($value);
// }
$this->listingListingCategories->removeElement($listingListingCategory);
}
/**
* Get categories
*
* #return \Doctrine\Common\Collections\Collection|ListingListingCategory[]
*/
public function getListingListingCategories()
{
return $this->listingListingCategories;
}
/**
* Set user
*
* #param \Cocorico\UserBundle\Entity\User $user
* #return Listing
*/
public function setUser(User $user = null)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \Cocorico\UserBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
/**
* Add images
*
* #param ListingImage $image
* #return Listing
*/
public function addImage(ListingImage $image)
{
$image->setListing($this); //Because the owning side of this relation is listing image
$this->images[] = $image;
return $this;
}
/**
* Remove images
*
* #param ListingImage $image
*/
public function removeImage(ListingImage $image)
{
$this->images->removeElement($image);
$image->setListing(null);
}
/**
* Get images
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getImages()
{
return $this->images;
}
/**
* Set location
*
* #param ListingLocation $location
* #return Listing
*/
public function setLocation(ListingLocation $location = null)
{
$this->location = $location;
//Needed to persist listing_id on listing_location table when inserting a new listing embedding a listing location form
$this->location->setListing($this);
}
/**
* Get location
*
* #return ListingLocation
*/
public function getLocation()
{
return $this->location;
}
/**
* Add discount
*
* #param ListingDiscount $discount
* #return Listing
*/
public function addDiscount(ListingDiscount $discount)
{
$discount->setListing($this);
$this->discounts[] = $discount;
return $this;
}
/**
* Remove discount
*
* #param ListingDiscount $discount
*/
public function removeDiscount(ListingDiscount $discount)
{
$this->discounts->removeElement($discount);
$discount->setListing(null);
}
/**
* Get discounts
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getDiscounts()
{
return $this->discounts;
}
/**
* #param ArrayCollection|ListingDiscount[] $discounts
*/
public function setDiscounts(ArrayCollection $discounts)
{
foreach ($discounts as $discount) {
$discount->setListing($this);
}
$this->discounts = $discounts;
}
/**
* #return \Doctrine\Common\Collections\Collection|Booking[]
*/
public function getBookings()
{
return $this->bookings;
}
/**
* #param ArrayCollection|Booking[] $bookings
*/
public function setBookings(ArrayCollection $bookings)
{
foreach ($bookings as $booking) {
$booking->setListing($this);
}
$this->bookings = $bookings;
}
/**
* Add booking
*
* #param Booking $booking
*
* #return Listing
*/
public function addBooking(Booking $booking)
{
$this->bookings[] = $booking;
return $this;
}
/**
* Remove booking
*
* #param Booking $booking
*/
public function removeBooking(Booking $booking)
{
$this->bookings->removeElement($booking);
}
/**
* #return mixed
*/
public function getThreads()
{
return $this->threads;
}
/**
* #param ArrayCollection|Thread[] $threads
*/
public function setThreads(ArrayCollection $threads)
{
foreach ($threads as $thread) {
$thread->setListing($this);
}
$this->threads = $threads;
}
/**
* Add thread
*
* #param Thread $thread
*
* #return Listing
*/
public function addThread(Thread $thread)
{
$this->threads[] = $thread;
return $this;
}
/**
* Remove thread
*
* #param Thread $thread
*/
public function removeThread(Thread $thread)
{
$this->threads->removeElement($thread);
}
/**
* Add ListingOption
*
* #param ListingOptionInterface $option
* #return Listing
*/
public function addOption($option)
{
$option->setListing($this);
$this->options[] = $option;
return $this;
}
/**
* Remove ListingOption
*
* #param ListingOptionInterface $option
*/
public function removeOption($option)
{
$this->options->removeElement($option);
}foreach ($threads as $thread) {
$thread->setListing($this);
}
$this->threads = $threads;
}
/**
* Add thread
*
* #param Thread $thread
*
* #return Listing
*/
public function addThread(Thread $thread)
{
$this->threads[] = $thread;
return $this;
}
/**
* Remove thread
*
* #param Thread $thread
*/
public function removeThread(Thread $thread)
{
$this->threads->removeElement($thread);
}
/**
* Add ListingOption
*
* #param ListingOptionInterface $option
* #return Listing
*/
public function addOption($option)
{
$option->setListing($this);
$this->options[] = $option;
return $this;
}
/**
* Remove ListingOption
*
* #param ListingOptionInterface $option
*/
public function removeOption($option)
{
$this->options->removeElement($option);
}
/**
* Get ListingOptions
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getOptions()
{
return $this->options;
}
/**
* #param ArrayCollection $options
* #return $this
*/
public function setOptions(ArrayCollection $options)
{
foreach ($options as $option) {
$option->setListing($this);
}
$this->options = $options;
return $this;
}
/**
* #param int $minImages
* #param bool $strict
*
* #return array
*/
public function getCompletionInformations($minImages, $strict = true)
{
$characteristic = 0;
foreach ($this->getListingListingCharacteristics() as $characteristics) {
if ($characteristics->getListingCharacteristicValue()) {
$characteristic = 1;
}
}
return array(
"title" => $this->getTitle() ? 1 : 0,
"description" => (
($strict && $this->getDescription()) ||
(!$strict && strlen($this->getDescription()) > 250)
) ? 1 : 0,
"price" => $this->getPrice() ? 1 : 0,
"image" => (
($strict && count($this->getImages()) >= $minImages) ||
(!$strict && count($this->getImages()) > $minImages)
) ? 1 : 0,
"characteristic" => $characteristic,
);
}
public function getTitle()
{
return (string)$this->translate()->getTitle();
}
public function getSlug()
{
return (string)$this->translate()->getSlug();
}
public function __toString()
{
return (string)$this->getTitle();
}
/**
* To add impersonating link into admin :
*
* #return User
*/
public function getImpersonating()
{
return $this->getUser();
}
}

symfony2.3, persist related entity with foregin key

i'm looking for nice way to persist 2 objects to db via doctrine in symfony 2.3
class CatController extends Controller
{
/**
* Creates a new Cat entity.
*
*/
public function createAction(Request $request)
{
$entity = new Cat();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity); <-- Split it or what ?
$em->flush();
return $this->redirect($this->generateUrl('cat_show', array('id' => $entity->getId())));
}
return $this->render('ViszmanCatBundle:Cat:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
when form is validated i can get post data and create 2 objects with that data but i think there should be clearer way to do this, above code is working not as i wanted, it only inserts foreign key to related entity when i do this:
class CatType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('meetings','collection',
array('type' => new MeetingType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
)
);
}
class MeetingType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$plDays = array('Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota', 'Niedziela');
$builder
->add('meetingDay', 'choice', array('choices' => $plDays))
->add('meetingTime', 'time',)
->add('cat', 'entity', array('class' => 'ViszmanCatBundle:Cat', 'property' => 'name'))
;
}
entities: Cat
namespace Viszman\CatBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Cat
*
* #ORM\Table()
* #ORM\Entity
*/
class Congregation
{
/**
* #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=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="Viszman\CatBundle\Entity\Member", mappedBy="cat")
*/
private $members;
/**
* #ORM\OneToMany(targetEntity="Viszman\CatBundle\Entity\Meeting", mappedBy="cat", cascade={"persist"})
*/
private $meetings;
public function __construct(){
$this->members = new \Doctrine\Common\Collections\ArrayCollection();
$this->meetings = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Cat
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add members
*
* #param \Viszman\CatBundle\Entity\Member $members
* #return Cat
*/
public function addMember(\Viszman\CatBundle\Entity\Member $members)
{
$this->members[] = $members;
return $this;
}
/**
* Remove members
*
* #param \Viszman\CatBundle\Entity\Member $members
*/
public function removeMember(\Viszman\CatBundle\Entity\Member $members)
{
$this->members->removeElement($members);
}
/**
* Get members
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getMembers()
{
return $this->members;
}
/**
* Add meetings
*
* #param \Viszman\CationBundle\Entity\Meeting $meetings
* #return Cat
*/
public function addMeeting(\Viszman\CatBundle\Entity\Meeting $meetings)
{
$this->meetings[] = $meetings;
return $this;
}
/**
* Remove meetings
*
* #param \Viszman\CatBundle\Entity\Meeting $meetings
*/
public function removeMeeting(\Viszman\CatBundle\Entity\Meeting $meetings)
{
$this->meetings->removeElement($meetings);
}
/**
* Get meetings
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getMeetings()
{
return $this->meetings;
}
namespace Viszman\CatBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Meeting
*
* #ORM\Table()
* #ORM\Entity
*/
class Meeting
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="meeting_day", type="smallint")
*/
private $meetingDay;
/**
* #var \DateTime
*
* #ORM\Column(name="meeting_time", type="time")
*/
private $meetingTime;
/**
* #ORM\ManyToOne(targetEntity="Viszman\CatBundle\Entity\Cat", inversedBy="meetings")
* #ORM\JoinColumn(name="cat_id", referencedColumnName="id")
*/
private $cat;
public function __construct()
{
$this->created = new \DateTime();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set meetingDay
*
* #param integer $meetingDay
* #return Meeting
*/
public function setMeetingDay($meetingDay)
{
$this->meetingDay = $meetingDay;
return $this;
}
/**
* Get meetingDay
*
* #return integer
*/
public function getMeetingDay()
{
return $this->meetingDay;
}
/**
* Set cat
*
* #param \Viszman\CatBundle\Entity\Cat $cat
* #return Member
*/
public function setCat(\Viszman\CatBundle\Entity\Cat $cat = null)
{
$this->cat = $cat;
return $this;
}
/**
* Get cat
*
* #return \stdClass
*/
public function getCat()
{
return $this->cat;
}
/**
* Set meetingTime
*
* #param \DateTime $meetingTime
* #return Meeting
*/
public function setMeetingTime($meetingTime)
{
$this->meetingTime = $meetingTime;
return $this;
}
/**
* Get meetingTime
*
* #return \DateTime
*/
public function getMeetingTime()
{
return $this->meetingTime;
}
this generate embedded form with unwanted data, meaning in Meeting section i need to choice Cat, but i dont want to, what i want is that meeting is on default attached to Cat on create, update. Do i need to change something in Cat or Meeting Entity? I don't know if i'm clear, sorry for my poor english
You need to remove this line in the MeetingType:
->add('cat', 'entity', array('class' => 'ViszmanCatBundle:Cat', 'property' => 'name'))
Then in yout Controller persist your Cat entity and yout Meeting entity (which you can find using cat's getMeetings method).
If you want both to be persisted in one shot, take a look at the cascade operation for Doctrine entities.

Data Transformer for Collection Field Type

I am creating a Chat system. I have two entities Chat and ChatThread.
namespace Acme\UitBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Chat
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Acme\UitBundle\Entity\ChatRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Chat
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="chats")
* #ORM\JoinColumn(name="uid", referencedColumnName="id")
*/
private $user;
/**
* #var string
*
* #ORM\Column(name="msg", type="text")
*/
private $msg;
/**
* #ORM\ManyToOne(targetEntity="ChatThread", inversedBy="chats")
* #ORM\JoinColumn(name="tid", referencedColumnName="id")
*/
private $thread;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set user
*
* #param \stdClass $user
* #return Chat
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \stdClass
*/
public function getUser()
{
return $this->user;
}
/**
* Set msg
*
* #param string $msg
* #return Chat
*/
public function setMsg($msg)
{
$this->msg = $msg;
return $this;
}
/**
* Get msg
*
* #return string
*/
public function getMsg()
{
return $this->msg;
}
/**
* Set date
*
* #param \DateTime $date
* #return Chat
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* #ORM\PrePersist
*/
public function setDateValue()
{
$this->date = new \DateTime();
}
/**
* Set thread
*
* #param \Acme\UitBundle\Entity\ChatThread $thread
* #return Chat
*/
public function setThread(\Acme\UitBundle\Entity\ChatThread $thread = null)
{
$this->thread = $thread;
return $this;
}
/**
* Get thread
*
* #return \Acme\UitBundle\Entity\ChatThread
*/
public function getThread()
{
return $this->thread;
}
}
Entity ChatThread :
namespace Acme\UitBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\OrderBy;
use Doctrine\Common\Collections\ArrayCollection;
/**
* ChatThread
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Acme\UitBundle\Entity\ChatThreadRepository")
*/
class ChatThread
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="inside")
*/
private $inside;
/**
* #ORM\OneToMany(targetEntity="Chat", mappedBy="thread")
* #OrderBy({"date" = "DESC"})
*/
private $chats;
public function __construct()
{
$this->chats=new ArrayCollection();
$this->inside=new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set inside
*
* #param array $inside
* #return ChatThread
*/
public function setInside($inside)
{
$this->inside = $inside;
return $this;
}
/**
* Get inside
*
* #return array
*/
public function getInside()
{
return $this->inside;
}
/**
* Add inside
*
* #param \Acme\UitBundle\Entity\User $inside
* #return ChatThread
*/
public function addInside(\Acme\UitBundle\Entity\User $inside)
{
$this->inside[] = $inside;
return $this;
}
/**
* Remove inside
*
* #param \Acme\UitBundle\Entity\User $inside
*/
public function removeInside(\Acme\UitBundle\Entity\User $inside)
{
$this->inside->removeElement($inside);
}
/**
* Add chats
*
* #param \Acme\UitBundle\Entity\Chat $chats
* #return ChatThread
*/
public function addChat(\Acme\UitBundle\Entity\Chat $chats)
{
$this->chats[] = $chats;
return $this;
}
/**
* Remove chats
*
* #param \Acme\UitBundle\Entity\Chat $chats
*/
public function removeChat(\Acme\UitBundle\Entity\Chat $chats)
{
$this->chats->removeElement($chats);
}
/**
* Get chats
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChats()
{
return $this->chats;
}
}
What I want to do is Take users to be added in inside and create a ChatThread and add a msg in Chat.
I have a form of chatType which takes user id's as collection with name thread. I have used a data transformer to transform inside into ChatThread.
namespace Acme\UitBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Acme\UitBundle\Form\DataTransformer\ThreadToInsideTransformer;
class ChatType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$entityManager = $options['em'];
$transformer = new ThreadToInsideTransformer($entityManager);
$builder->add(
$builder->create('thread', 'collection' ,array('allow_add' => true,'label' => false))->addModelTransformer($transformer)
);
$builder->add('msg','textarea');
/* $builder->add('inside','collection',array(
'allow_add' => true,
'allow_delete' => true)); */
$builder->add('hook', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Acme\UitBundle\Entity\Chat'
))
->setRequired(array(
'em',
))
->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
));
}
public function getName() {
return 'chat';
}
}
data transformer
namespace Acme\UitBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\UitBundle\Entity\ChatThread;
class ThreadToInsideTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* Transforms an object (thread) to an array (inside).
*
* #param Thread $thread
* #return array
*/
public function transform($thread)
{
if (null === $thread) {
return "";
}
return $thread->getInside();
}
/**
* Transforms a array (inside) to an object (thread).
*
* #param array $inside
*
* #return Thread
*
* #throws Create new Thread if object (thread) is not found.
*/
public function reverseTransform($inside)
{
if (!is_array($inside)) {
return null;
}
$a=$this->om
->getRepository('AcmeUitBundle:User')
->findById($inside);
if (null === $a) {
throw new TransformationFailedException(sprintf(
'Error. . .'
));
}
$thread = $this->om
->getRepository('AcmeUitBundle:ChatThread')
->findByInsideChat($a)
;
if (null === $thread) {
$thread=new ChatThread();
$thread->setInside($inside);
$this->om->persist($thread);
$this->om->flush();
}
return $thread;
}
}
When I submit form, I get this error Expected argument of type "object, array or empty", "string" given.

Persist Many to Many relationship in different steps

Hi I try to solve this problem now for days. But I don't get it so I hope you can help me.
I have the entity Worker and it's in a many to many relation ship with the Task entity.
Now I don't want my user to do all input at one time. At first the user should enter his details and if they are valid he can choose his tasks.
So I added three methods to the standard generated code and change the url path in the createAction().
But if I press the submit button in my AddTaskType form there is nothing persisted to the database.
I hope you have suggestions for this problem.
//Worker Entity
namespace DLRG\HelferBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #ORM\Table(name="Workers")
*/
class Worker {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
protected $name;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
protected $lastName;
/**
* #ORM\Column(type="string")
*/
protected $tel;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank()
* #Assert\Email()
*/
protected $email;
/**
* #ORM\Column(type="string")
*/
protected $organisation;
/**
* #ORM\Column(type="integer")
* #Assert\NotBlank()
* #Assert\Range(min=6, minMessage="Du musst mindestens 6 Jahre Alt sein um bei uns helfen zu können.")
*/
protected $age;
/**
* #ORM\ManyToMany(targetEntity="Task", inversedBy="workers")
* #ORM\JoinTable(name="workers_tasks")
*/
protected $tasks;
/**
* #ORM\ManyToMany(targetEntity="Time", inversedBy="workers")
* #ORM\JoinTable(name="workers_times")
*/
protected $times;
/**
* Constructor
*/
public function __construct() {
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection ();
$this->times = new \Doctrine\Common\Collections\ArrayCollection ();
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Worker
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName() {
return $this->name;
}
/**
* Set lastName
*
* #param string $lastName
* #return Worker
*/
public function setLastName($lastName) {
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* #return string
*/
public function getLastName() {
return $this->lastName;
}
/**
* Set tel
*
* #param string $tel
* #return Worker
*/
public function setTel($tel) {
$this->tel = $tel;
return $this;
}
/**
* Get tel
*
* #return string
*/
public function getTel() {
return $this->tel;
}
/**
* Set email
*
* #param string $email
* #return Worker
*/
public function setEmail($email) {
$this->email = $email;
return $this;
}
/**
* Get email
*
* #return string
*/
public function getEmail() {
return $this->email;
}
/**
* Set organisation
*
* #param string $organisation
* #return Worker
*/
public function setOrganisation($organisation) {
$this->organisation = $organisation;
return $this;
}
/**
* Get organisation
*
* #return string
*/
public function getOrganisation() {
return $this->organisation;
}
/**
* Set age
*
* #param integer $age
* #return Worker
*/
public function setAge($age) {
$this->age = $age;
return $this;
}
/**
* Get age
*
* #return integer
*/
public function getAge() {
return $this->age;
}
/**
* Add tasks
*
* #param \DLRG\HelferBundle\Entity\Task $tasks
* #return Worker
*/
public function addTask(\DLRG\HelferBundle\Entity\Task $task) {
$task->addWorker ( $this );
$this->tasks [] = $task;
return $this;
}
/**
* Remove tasks
*
* #param \DLRG\HelferBundle\Entity\Task $tasks
*/
public function removeTask(\DLRG\HelferBundle\Entity\Task $tasks) {
$this->tasks->removeElement ( $tasks );
}
/**
* Get tasks
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTasks() {
return $this->tasks;
}
/**
* Add times
*
* #param \DLRG\HelferBundle\Entity\Time $times
* #return Worker
*/
public function addTime(\DLRG\HelferBundle\Entity\Time $time) {
$time->addWorker ( $this );
$this->times [] = $time;
return $this;
}
/**
* Remove times
*
* #param \DLRG\HelferBundle\Entity\Time $times
*/
public function removeTime(\DLRG\HelferBundle\Entity\Time $times) {
$this->times->removeElement ( $times );
}
/**
* Get times
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTimes() {
return $this->times;
}
}
//TaskEntity
namespace DLRG\HelferBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="Tasks")
*/
class Task {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\Column(type="string")
*/
protected $description;
/**
* #ORM\ManyToMany(targetEntity="Time", mappedBy="tasks")
*/
protected $times;
/**
* #ORM\ManyToMany(targetEntity="Worker", mappedBy="tasks")
*/
protected $workers;
public function __toString() {
return $this->name;
}
/**
* Constructor
*/
public function __construct() {
$this->times = new \Doctrine\Common\Collections\ArrayCollection ();
$this->workers = new \Doctrine\Common\Collections\ArrayCollection ();
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Task
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName() {
return $this->name;
}
/**
* Set description
*
* #param string $description
* #return Task
*/
public function setDescription($description) {
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription() {
return $this->description;
}
/**
* Add times
*
* #param \DLRG\HelferBundle\Entity\Time $times
* #return Task
*/
public function addTime(\DLRG\HelferBundle\Entity\Time $time) {
$this->times [] = $time;
return $this;
}
/**
* Remove times
*
* #param \DLRG\HelferBundle\Entity\Time $times
*/
public function removeTime(\DLRG\HelferBundle\Entity\Time $times) {
$this->times->removeElement ( $times );
}
/**
* Get times
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTimes() {
return $this->times;
}
/**
* Add workers
*
* #param \DLRG\HelferBundle\Entity\Worker $workers
* #return Task
*/
public function addWorker(\DLRG\HelferBundle\Entity\Worker $worker) {
$this->workers [] = $worker;
return $this;
}
/**
* Remove workers
*
* #param \DLRG\HelferBundle\Entity\Worker $workers
*/
public function removeWorker(\DLRG\HelferBundle\Entity\Worker $workers) {
$this->workers->removeElement ( $workers );
}
/**
* Get workers
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getWorkers() {
return $this->workers;
}
}
The added methods
//WorkerController.php
/**
* Add task to a Worker
*
* #Route("/", name="task_add")
* #Method("POST")
* #Template("DLRGHelferBundle:Task:add.html.twig")
*/
public function addTask(Request $request, $id) {
$em = $this->getDoctrine ()->getManager ();
$entity = $em->getRepository ( 'DLRGHelferBundle:Worker' )->find ( $id );
if (! $entity) {
throw $this->createNotFoundException ( 'Unable to find Worker entity.' );
}
$form = $this->createAddTaskForm ( $entity );
$form->handleRequest ( $request );
if ($form->isValid ()) {
$formData = $form->get ( 'tasks' )->getData ();
foreach ( $formData as $task ) {
$entity->addTask ( $task );
}
$em->persist ( $entity );
$em->flush ();
return $this->redirect ( $this->generateUrl ( 'worker_show', array (
'id' => $entity->getId ()
) ) );
}
return array (
'entity' => $entity,
'form' => $form->createView ()
);
}
/**
* Creates a form to add a Task to Worker entity.
*
* #param Worker $entity
* The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createAddTaskForm(Worker $entity) {
$form = $this->createForm ( new AddTaskType (), $entity, array (
'action' => $this->generateUrl ( 'task_add', array (
'id' => $entity->getId ()
) ),
'method' => 'POST'
) );
$form->add ( 'submit', 'submit', array (
'label' => 'Zeiten'
) );
return $form;
}
/**
* Displays a form to create a new Worker entity.
*
* #Route("/new/{id}/addTask", name="worker_task")
* #Method("GET")
* #Template()
*/
public function addAction($id) {
$em = $this->getDoctrine ()->getManager ();
$entity = $em->getRepository ( 'DLRGHelferBundle:Worker' )->find ( $id );
if (! $entity) {
throw $this->createNotFoundException ( 'Unable to find Worker entity.' );
}
$form = $this->createAddTaskForm ( $entity );
return array (
'entity' => $entity,
'form' => $form->createView ()
);
}
My AddTaskType looks like this:
namespace DLRG\HelferBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use DLRG\HelferBundle\Controller\HelferController;
class AddTaskType extends AbstractType {
/**
*
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add ( 'tasks', 'entity', array (
'class' => 'DLRGHelferBundle:Task',
'property' => 'name',
'multiple' => true
) );
}
/**
*
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults ( array (
'data_class' => 'DLRG\HelferBundle\Entity\Worker'
) );
}
/**
*
* #return string
*/
public function getName() {
return 'dlrg_helferbundle_addTask';
}
}

Combine Questionnaire with User entity in form - symfony2

I need to add questionnaire of multiple choice questions to my registration form. The questions and options are in two entities:
<?php
namespace Me\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Question
*
* #ORM\Table(name="question")
* #ORM\Entity(repositoryClass="Me\UserBundle\Entity\QuestionRepository")
*/
class Question
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="questionText", type="text")
*/
private $questionText;
/**
* #var boolean $expanded
*
* #ORM\Column(name="expanded", type="boolean")
*/
private $expanded;
/**
* #var boolean $multiple
*
* #ORM\Column(name="multiple", type="boolean")
*/
private $multiple;
/**
* #var Questionnaire $questionnaire
*
* #ORM\ManyToOne(targetEntity="Questionnaire", inversedBy="questions")
* #ORM\JoinColumn(name="questionnaire", referencedColumnName="id", onDelete="cascade")
*/
private $questionnaire;
/**
* #var \Doctrine\Common\Collections\ArrayCollection $options
*
* #ORM\OneToMany(targetEntity="Option", mappedBy="question", cascade={"all"})
*/
private $options;
public function __construct()
{
$this->expanded = false;
$this->multiple = false;
$this->options = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set questionText
*
* #param string $questionText
* #return Question
*/
public function setQuestionText($questionText)
{
$this->questionText = $questionText;
return $this;
}
/**
* Get questionText
*
* #return string
*/
public function getQuestionText()
{
return $this->questionText;
}
/**
* #param mixed $options
*/
public function setOptions($options)
{
$this->options[] = $options;
return $this;
}
/**
* #return mixed
*/
public function getOptions()
{
return $this->options;
}
function __toString()
{
return $this->getQuestionText();
}
/**
* #param boolean $expanded
*/
public function setExpanded($expanded)
{
$this->expanded = $expanded;
}
/**
* #return boolean
*/
public function getExpanded()
{
return $this->expanded;
}
/**
* #param boolean $multiple
*/
public function setMultiple($multiple)
{
$this->multiple = $multiple;
}
/**
* #return boolean
*/
public function getMultiple()
{
return $this->multiple;
}
/**
* #param \Me\UserBundle\Entity\Questionnaire $questionnaire
*/
public function setQuestionnaire($questionnaire)
{
$this->questionnaire = $questionnaire;
}
/**
* #return \Me\UserBundle\Entity\Questionnaire
*/
public function getQuestionnaire()
{
return $this->questionnaire;
}
}
and
<?php
namespace Me\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* QuestionOption
*
* #ORM\Table(name="option")
* #ORM\Entity(repositoryClass="Me\UserBundle\Entity\OptionRepository")
*/
class Option
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="questionId", type="integer")
*/
private $questionId;
/**
* #var string
*
* #ORM\Column(name="optionText", type="string", length=255)
*/
private $optionText;
/**
* #ORM\ManyToOne(targetEntity="Question", inversedBy="options")
* #ORM\JoinColumn(name="questionId", referencedColumnName="id", onDelete="cascade")
**/
private $question;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set optionText
*
* #param string $optionText
* #return Option
*/
public function setOptionText($optionText)
{
$this->optionText = $optionText;
return $this;
}
/**
* Get optionText
*
* #return string
*/
public function getOptionText()
{
return $this->optionText;
}
/**
* #return mixed
*/
public function getQuestion()
{
return $this->question;
}
/**
* #param mixed $question
*/
public function setQuestion($question)
{
$this->question = $question;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
function __toString()
{
return $this->getOptionText();
}
}
I also have a questionnaire entity, though I don't think I really need it because users won't be creating questionnaires, only filling the single questionnaire during registration.
My user entity:
<?php
namespace Me\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Me\UserBundle\Entity\UserRepository")
*/
class User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="firstName", type="string", length=50)
*/
private $firstName;
/**
* #var string
*
* #ORM\Column(name="middleInitial", type="string", length=50)
*/
private $middleInitial;
/**
* #var string
*
* #ORM\Column(name="lastName", type="string", length=50)
*/
private $lastName;
/**
* #var string
*
* #ORM\Column(name="homePhoneArea", type="string", length=3)
*/
private $homePhoneArea;
/**
* #var string
*
* #ORM\Column(name="homePhoneNumber", type="string", length=7)
*/
private $homePhoneNumber;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=50)
*/
private $email;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*/
public $questions;
public function __construct()
{
$this->questions = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
* #return User
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set middleInitial
*
* #param string $middleInitial
* #return User
*/
public function setMiddleInitial($middleInitial)
{
$this->middleInitial = $middleInitial;
return $this;
}
/**
* Get middleInitial
*
* #return string
*/
public function getMiddleInitial()
{
return $this->middleInitial;
}
/**
* Set lastName
*
* #param string $lastName
* #return User
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* #return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Set homePhoneArea
*
* #param string $homePhoneArea
* #return User
*/
public function setHomePhoneArea($homePhoneArea)
{
$this->homePhoneArea = $homePhoneArea;
return $this;
}
/**
* Get homePhoneArea
*
* #return string
*/
public function getHomePhoneArea()
{
return $this->homePhoneArea;
}
/**
* Set homePhoneNumber
*
* #param string $homePhoneNumber
* #return User
*/
public function setHomePhoneNumber($homePhoneNumber)
{
$this->homePhoneNumber = $homePhoneNumber;
return $this;
}
/**
* Get homePhoneNumber
*
* #return string
*/
public function getHomePhoneNumber()
{
return $this->homePhoneNumber;
}
/**
* Set email
*
* #param string $email
* #return User
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Get email
*
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getQuestions()
{
return $this->questions;
}
}
My user controller:
public function newAction()
{
$user = new User();
$em = $this->getDoctrine()->getManager();
$questions = $em->getRepository('MeUserBundle:Question')->findAll();
if (!$questions) {
throw $this->createNotFoundException('Unable to find Questions.');
}
$builder = $this->createFormBuilder();
$optionEntities = array();
foreach ($questions as $question)
{
$options = array();
foreach ($question->getOptions() as $option)
{
$options[$option->getId()] = $option->getOptionText();
$optionEntities[$option->getId()] = $option;
}
$builder->add('question_'. $question->getId(), 'choice', array(
'label' => $question->getQuestionText(),
'expanded' => $question->getExpanded(),
'multiple' => $question->getMultiple(),
'choices' => $options
));
}
$user->getQuestions()->add($questions);
$form = $this->createForm(new MyFormType(), array('User' => $user));
return $this->render('MeUserBundle:User:new.html.twig', array(
'entity' => $user,
'form' => $form->createView(),
));
}
The form type as it stands today:
<?php
namespace Me\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class MyFormType extends AbstractType
{
protected $questions;
public function __construct( $questions)
{
$this->questions = $questions;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('questions', 'collection', array(
'type' => new QuestionType()
))
->add('firstName')
->add('middleInitial')
->add('lastName')
->add('homePhoneArea')
->add('homePhoneNumber')
->add('email')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
));
}
public function getName()
{
return 'myform_type';
}
}
This setup doesn't work to get the questions and their associated options, and display them in the same user creation form. I've seen instructions and docs for combining forms, but not creating forms with this kind of configuration. Any guidance would be appreciated.
I'm not sure if I've understood your question correctly but maybe this could help.
From what I see you're building the form(for questions) but you're not using it anywhere! The easiest way is to pass the questions(in your case $user->getQuestions() ) to the MyFormType and the add all the questions inside of the type.
So it would look like something like this
$this->createForm(new MyFormType($user->getQuestions()), array('User' => $user));
And inside your type
protected $questions;
public function __construct($questions)
{
$this->questions = $questions;
}
protected $questions;
public function __construct($questions)
{
$this->questions = $questions;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach ($this->questions as $question)
{
$options = array();
foreach ($question->getOptions() as $option)
{
$options[$option->getId()] = $option->getOptionText();
$optionEntities[$option->getId()] = $option;
}
$builder->add('question_'. $question->getId(), 'choice', array(
'label' => $question->getQuestionText(),
'expanded' => $question->getExpanded(),
'multiple' => $question->getMultiple(),
'choices' => $options
));
}
}
Edit 1
Why don't you try the following?
Add the method setQuestionnaire in User and create a type called QuestionnaireType which is responsible to create the whole questionnaire
In the UserType(sorry for the wrong names) add the QuestionnaireType as an embedded form
Once the user submits the data and you call the bind symfony will pass the whole questionnaire object to the created method so you can iterate over it and save the user's aswers!
Your users entity needs a relation to his $answers you should store in an $answers-field in your user entity, (look up "embedding collections")
Then in your controller that digests the form your store the values by $user->setAnswers(value)) and then you'll find the answers values in the users entity's $answers field ($user->getAnswers()).
ANd dont forget to add your getters and setters.
$php app/console doctrine:generate:entities BundleName:Entityname
At first you could add a setQuestions method in user, this method will receive an ArrayCollection.
Then have you got a look at http://symfony.com/doc/current/reference/forms/types/collection.html ?
And maybe you could ask yourself what does this form should display/do
Does it need to embbed a collection or a list of QuestionFormType could be ok?
Each time i have to do embedded form type, i draw sketches and activity diagram for being sure to not dive into a to complex form structure

Categories