Symfony FOS Rest Bundle Api呼叫多对多关系

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.