REST API Linking a company to another company FOSRestBundle - php

In a REST API that I'm developing a Company has a parent attribute which is also of the Company Class.
That way I can create a three of Companies. A Company has one parent Company (Company Class) and can have multiple children Companies (Collection)
/**
* #ORM\ManyToOne(targetEntity="Company", inversedBy="child")
* #Expose()
*/
protected $parent;
/**
* #ORM\OneToMany(targetEntity="Company", mappedBy="parent")
*/
protected $child;
public function __construct()
{
...
$this->child = new \Doctrine\Common\Collections\ArrayCollection();
}
How would I go about and make/remove that relationship of parents and children companies?
I've read about the LINK verb but I'm afraid it's not supported by all webservers.
Should I set the relationship with PUT?
How would I then remove the relationship to the parent (set it to NULL).
My CompanyController looks like this:
/**
* Edit Action
*
* #Rest\View()
*/
public function editAction(Company $company)
{
return $this->processForm($company);
}
/**
* Remove Action
*
* #Rest\View(statusCode=204)
*/
public function removeAction(Company $company)
{
$em = $this->getDoctrine()->getManager();
$em->remove($company);
$em->flush();
}
/**
* ProcessForm Action
*/
private function processForm(Company $company)
{
$statusCode = $this->getRequest()->getMethod() == 'POST' ? Codes::HTTP_CREATED : Codes::HTTP_SEE_OTHER;
$form = $this->createForm(new CompanyType(), $company);
$form->bind($this->getRequest());
if($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$em->persist($company);
$em->flush();
return $this->redirectView(
$this->generateUrl(
'company_get',
array('id' => $company->getId())
),
$statusCode
);
}
return View::create($form, 400);
}
Any suggestions?

Related

CollectionType add action for database flush

I've created CollectionType form, which can add unlimited amount of TicketTime entities into one Ticket entity. It is connected via database by a OneToMany relation.
What I am trying to do is to add one Ticket and multiple TicketTimes to the database at one time. For example, one Ticket and 3 TicketTimes.
I would like to know how to make this method, so I can take one Ticket & multiple TicketTimes and flush them to the database.
My Controller action:
public function createTicket(Request $request)
{
if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
if ($this->getUser()->getrole() == 3) {
$em = $this->getDoctrine()->getManager();
$ticket = new Ticket();
$timeArray = new ArrayCollection();
$form = $this->createForm(TicketType::class, $ticket);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($timeArray as $time) {
$time->setTicket($ticket);
$em->persist($time);
}
$ticket->setTaken(0);
$em->persist($ticket);
$em->flush();
return $this->redirectToRoute('main');
}
return $this->render(
'create_ticket.html.twig',
array('form' => $form->createView())
);
}
}
return $this->redirectToRoute('main');
}
Ticket entity:
/**
* #Assert\NotBlank()
* #ORM\OneToMany(targetEntity="TicketTime", mappedBy="ticket", fetch="EXTRA_LAZY", orphanRemoval=true, cascade={"persist"})
*/
private $ticketTimes;
public function setTicketTimes($time): void
{
$this->ticketTimes = $time;
}
/**
* #return ArrayCollection|TicketTime[]
*/
public function getTicketTimes()
{
return $this->ticketTimes;
}
TicketTime entity:
/**
* #ORM\ManyToOne(targetEntity="Ticket", inversedBy="time")
* #ORM\JoinColumn(nullable=false)
*/
private $ticket;
public function getTicket()
{
return $this->ticket;
}
public function setTicket($ticket): void
{
$this->ticket = $ticket;
}
I managed to fix it by myself :)
What was wrong:
I needed to create add method in Ticket entity, so I will be able to add as many TicketTime's as I want, here's the code which I added into Ticket entity:
public function addTicketTime(TicketTime $time)
{
$this->ticketTimes[] = $time;
$time->setTicket($this);
return $this;
}
I've also added some lines into Controller
........
if ($form->isSubmitted() && $form->isValid()) {
$ticketTimes = $form->get('ticketTimes')->getData();
foreach ($ticketTimes as $time) {
$ticket->addTicketTime($time);
}
.......

foreign key null in collection form symfony 3

i have two entities Survey.php and Choice.php I create a form to add new survey and multi choices, I used a many-to-one relation between the two entities, the problem is when I submit for a new survey, the foreign key of choice entity return null
here's my code
Survey.PHP
/**
* Survey
*
* #ORM\Table(name="survey")
* #ORM\Entity(repositoryClass="AppBundle\Repository\SurveyRepository")
*/
class Survey
{
/....
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Choice", mappedBy="survey_id",cascade="persist")
* #ORM\JoinColumn(nullable=false, referencedColumnName="id")
*/
private $choice;
public function __construct()
{
$this->choice = new ArrayCollection();
}
/**
* Add choice
*
* #param \AppBundle\Entity\Choice $choice
*
* #return Survey
*/
public function addChoice(\AppBundle\Entity\Choice $choice)
{
$this->choice[] = $choice;
$choice->setSurvey($this);
return $this;
}
/**
* Remove choice
*
* #param \AppBundle\Entity\Choice $choice
*/
public function removeChoice(\AppBundle\Entity\Choice $choice)
{
$this->choice->removeElement($choice);
}
/**
* Get choice
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChoice()
{
return $this->choice;
}
}
Choice.php
/**
* Choice
* #ORM\Table(name="choice")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ChoiceRepository")
*/
class Choice
{
/**
* #var int
* #ORM\ManyToOne(targetEntity="Survey",inversedBy="choice")
*/
private $survey;
/**
* Set survey
*
* #param \AppBundle\Entity\Survey $survey
*
* #return Choice
*/
public function setSurveyId(\AppBundle\Entity\Survey $survey)
{
$this->survey = $survey;
return $this;
}
/**
* Get surveyId
*
* #return \AppBundle\Entity\Survey
*/
public function getSurveyId()
{
return $this->survey_id;
}
}
SurveyController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Survey;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* Survey controller.
*
*/
class SurveyController extends Controller
{
/**
* Creates a new survey entity.
* #param Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function newAction(Request $request)
{
$survey = new Survey();
//
$form = $this->createForm('AppBundle\Form\SurveyType', $survey);
$form->handleRequest($request);
$survey->setCreateDate(new \DateTime('NOW'));
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($survey);
$em->flush();
return $this->redirectToRoute('survey_show', ['id' => $survey->getId()]);
}
return $this->render('survey/new.html.twig', [
'survey' => $survey,
'form' => $form->createView(),
]);
}
any suggestion, btw I think the problem is in the getters and setters )
Link the choice to the survey:
// Survey
public function addChoice(\AppBundle\Entity\Choice $choice)
{
$this->choice[] = $choice;
$choice->setSurvey($this);
return $this;
}
And change the survey_id stuff to survey. Dealing with objects not ids. And of course Survey::choice should be Survey::choices. The name changes might seem minor but will make your easier to maintain.
I fixed the problem by adding a for each loop inside the SurveyController.php
and it works just fine
SurveyController.php
if ($form->isSubmitted() && $form->isValid())
{
foreach ($survey->getChoices() as $choice){
$choice->setSurvey($survey);
}
$em = $this->getDoctrine()->getManager();
$em->persist($survey);
$em->flush();
not "THE best solution" but it gets the job done
It worked with me, but I had to remove the explicit foreign key mapping with the "inversedBy" setting from the class definition. I use a composite foreign key (using two columns), which maybe makes things harder though...

Expected value of type Entity for association field got "string" instead

I'm trying to insert into my Reviews table, within my controller I have:
public function indexAction(Request $request, $id)
{
if($id != null)
{
// Create a new Review entity
$review = new Review();
$form = $this->createForm(ReviewType::class, $review,[
'action' => $request->getUri()
]);
$form->handleRequest($request);
if($form->isValid()) {
$manager = $this->getDoctrine()->getManager();
$review->setPosted(new \DateTime());
$review->setBookID($id);
$review->setUserID($this->getUser());
$manager->persist($review);
$manager->flush();
}
return $this->render('ReviewBookBundle:Book:index.html.twig',
['form' => $form->createView());
}
}
However on the line $review->setBookID($id); i get this error:
Expected value of type "Review\BookBundle\Entity\Book" for association field "Review\ReviewsBundle\Entity\Review#$bookID", got "string" instead.
How do i overcome this issues? Since I've tried creating a Book entity and setting the bookID and then passing the Book entity into the $review-setBookID, like so:
$review->setBookID($book);
but that still doesn't work?
Can you try this:
Add this in your Review Entity:
/**
* #var Book
* #ORM\ManyToOne(targetEntity="YourBundle\Entity\Book", inversedBy="review", fetch="LAZY")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
/**
* #return Book
*/
public function getBook()
{
return $this->book;
}
/**
* #param $book
*/
public function setBook($book)
{
$this->book = $book;
}
And use:
$review->setBook($book);
$book must be instance of Book Entity
Edit
Book Entity:
public function __construct() {
$this->reviews = new ArrayCollection();
}
/**
* #var Review
* #ORM\OneToMany(targetEntity="YourBundle\Entity\Review", fetch="LAZY")
* #ORM\JoinColumn(name="review_id", mappedBy="book", referencedColumnName="id")
*/
protected $reviews;
/**
* #return Review
*/
public function getReviews()
{
return $this->reviews;
}
/**
* #param Review $review
*/
public function addReview(Review $review)
{
$this->reviews->add($review);
}
/**
* #param Review $review
*/
public function removeReview(Review $review)
{
$this->reviews->removeElement($review);
}
You should use $review->addBook() and pass book object not ID.

Symfony2 argument 1 passed must be a type of array, object given error

A simple problem that has many answers on SO... Yet none of them work on my project... So I get this error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in C:\wamp\www\Dig\front\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php on line 528 and defined in C:\wamp\www\Digidis\front\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php line 48
This happens everytime I create a new Email and try to save it in the database. The email is in a relationship with skin..
This is how I try to save it:
/**
* #Route("/{skin_id}/new", name="cms_email_new")
* #Method({"GET"})
* #Template()
*/
public function newAction($skin_id) {
$skin = $this->getRepository('ProjectSkinBundle:Skin')->find($skin_id);
$item = new Email();
$form = $this->createForm(new EmailType($this->container->getParameter("langs")), $item);
return array('form' => $form->createView(), 'item' => $item, 'skin' => $skin_id);
}
/**
* #Route("/{skin_id}/save", name="cms_email_save")
* #Template("ProjectUserBundle:EmailAdmin:new.html.twig")
* #Method({"POST"})
*/
public function saveAction(Request $request, $skin_id) {
$skin = $this->getRepository('ProjectSkinBundle:Skin')->find($skin_id);
$item = new Email();
$type = new EmailType($this->container->getParameter("langs"));
$form = $this->createForm($type, $item);
$form->handleRequest($request);
$em = $this->getEntityManager();
if ($form->isValid()) {
$this->upload($form, $item);
$skin->setEmailId($item);
$item->setSkin($skin); /// the error is here
$em->persist($skin);
$em->persist($item);
$em->flush();
return $this->redirect($this->generateUrl('cms_skin_email_edit', array('skin_id' => $skin_id)));
}
return array('form' => $form->createView(), 'item' => $item);
}
So by doing some testing I found out that this line is causing the problem:
$item->setSkin($skin);
Without this line everything works like a charm. However I need this line to work.
So this is the Entity with the setSkin method:
/**
*
* #ORM\OneToMany(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
/**
* Set skin
*
* #param \Project\SkinBundle\Entity\Skin $skin
* #return Email
*/
public function setSkin(\Project\SkinBundle\Entity\Skin $skin = null)
{
$this->skin = $skin;
return $this;
}
/**
* Get skin
*
* #return \Project\SkinBundle\Entity\Skin
*/
public function getSkin()
{
return $this->skin;
}
So what can I do to make his object become an array?
I have this little line but id doesnt help me :
public function __construct()
{
$this->skin = new ArrayCollection();
}
The form for creating a new email is this:
public function buildForm(FormBuilderInterface $builder, array $option) {
$builder->add('title', 'text', array('label' => 'cms.Title'));
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'Project\UserBundle\Entity\Email',
);
}
public function getName()
{
return 'my_email';
}
}
The $skin property is a One to Many relationship in your doctrine mapping. Doctrine is expecting an ArrayCollection object or array.
This is causing your exception:
/**
*
* #ORM\OneToMany(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
If you need a one to many relationship you should pass an array instead of a single object because you can have multiple skins. If you want a one to one relationship (a single skin per entity) you should change you doctrine mapping.
Possible solution 1:
public function __construct()
{
$this->skin = new ArrayCollection();
}
/**
* Set skin
*
* #param \Project\SkinBundle\Entity\Skin $skin
* #return Email
*/
public function setSkin(array $skin)
{
$this->skin = $skin;
return $this;
}
/**
* Get skin
*
* #return \Project\SkinBundle\Entity\Skin[]|ArrayCollection
*/
public function getSkin()
{
return $this->skin;
}
Possible solution 2 (OneToOne, but this could be a ManyToOne, that's up to you):
/**
*
* #ORM\OneToOne(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
You could prevent the error by simply wrapping the object (which you should confirm is an "Email" object) in an array:
$item->setSkin(array($skin));
However something else is going wrong here and the error is coming from when Doctrine compiles a unit-of-work to save to the database.
The skin relationship declartion of the Email entity is incorrect. The Join column declaration should be on the manyToOne side, so Email should be:
Email entity:
/*
* #ORM\OneToMany(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email")
*/
protected $skins;
Skin entity:
/*
* #ORM\ManyToOne(targetEntity="Project\SkinBundle\Entity\Email", inversedBy="emails")
* #ORM\JoinColumn(name="email_id", referencedColumnName="id")
*/
protected $email
Running app/console doctrine:generate:entities SkinBundle:Email (or however the entity is referenced) will then generate a methods like addSkin(Skin $skin) which are used to add objects to the relationship.
More info can be found on Doctrine associations.
For a one to many relationship you should have and be using methods addSkin() and removeSkin() in place of setSkin(). Also, as a convention I recommend pluralising collection properties i.e. $skin -> $skins. It makes the code clearer and errors in declaring and using entities become more obvious.
So for your entity that has many $skins I would recommend:
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $skins;
/**
* Constructor
*/
public function __construct()
{
$this->skins = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add skin
*
* #param Skin $skin
* #return Email
*/
public function addSkin(Skin $skin)
{
$this->skins[] = $skin;
return $this;
}
/**
* Remove skin
*
* #param Skin $skin
*/
public function removeSkin(Skin $skin)
{
$this->skins->removeElement($skin);
}
/**
* Get skins
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSkins()
{
return $this->skins;
}
Then where you have:
$item->setSkin($skin);
You should instead use:
$item->addSkin($skin);

How to update a manyToMany collection of an entity in onFlush event listener?

I have this entity:
<?php
namespace Comakai\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM,
Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Stuff {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(type="text")
* #Assert\NotBlank()
*/
private $content;
/**
* #ORM\ManyToMany(targetEntity="Apple", cascade={"persist"})
*/
private $apples;
/**
* #ORM\ManyToMany(targetEntity="Pig")
*/
private $pigs;
public function __construct() {
$this->apples = new \Doctrine\Common\Collections\ArrayCollection();
$this->pigs = new \Doctrine\Common\Collections\ArrayCollection();
}
public function setApples($apples) {
$this->getApples()->clear();
foreach ($apples as $apple) {
$this->addApple($apple);
}
}
public function setPigs($pigs) {
$this->getPigs()->clear();
foreach ($pigs as $pig) {
$this->addPig($pig);
}
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set content
*
* #param text $content
*/
public function setContent($content) {
$this->content = $content;
}
/**
* Get content
*
* #return text
*/
public function getContent() {
return $this->content;
}
/**
* Add apples
*
* #param Comakai\MyBundle\Entity\Apple $apples
*/
public function addApple(\Comakai\MyBundle\Entity\Apple $apples) {
$this->apples[] = $apples;
}
/**
* Get apples
*
* #return Doctrine\Common\Collections\Collection
*/
public function getApples() {
return $this->apples;
}
/**
* Add pigs
*
* #param Comakai\MyBundle\Entity\Pig $pigs
*/
public function addPig(\Comakai\MyBundle\Entity\Pig $pigs) {
$this->pigs[] = $pigs;
}
/**
* Get pigs
*
* #return Doctrine\Common\Collections\Collection
*/
public function getPigs() {
return $this->pigs;
}
}
and this listener:
<?php
namespace Comakai\MyBundle\Listener;
use Comakai\MyBundle\Util\SluggerParser
Doctrine\ORM\Event\OnFlushEventArgs,
Comakai\MyBundle\Entity\Stuff,
Comakai\MyBundle\Entity\Apple,
Comakai\MyBundle\Entity\Pig;
class Listener {
/**
* #param \Doctrine\ORM\Event\OnFlushEventArgs $ea
*/
public function onFlush(OnFlushEventArgs $ea) {
$em = $ea->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
$this->save($entity, $em, $uow);
}
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
$this->save($entity, $em, $uow);
}
}
public function save($entity, $em, $uow) {
if ($entity instanceof Stuff) {
$pigRepository = $em->getRepository('Comakai\MyBundle\Entity\Pig');
$content = $entity->getContent();
preg_match_all('/## pig:(\d+) ##/i', $content, $matches);
$entity->getPigs()->clear();
foreach($matches[1] as $pigID) {
$pig = $pigRepository->find($pigID);
if(!empty($pig)) {
$entity->addPig($pig);
}
}
$entity->setContent($content);
$meta = $em->getClassMetadata(get_class($entity));
$uow->recomputeSingleEntityChangeSet($meta, $entity);
$uow->computeChangeSet($meta, $entity);
}
}
}
And it works fine if apple's collection is empty, but if it has some item I get a duplication error.
How can I tell to the UnitOfWork that I only want to recalculate the pig's collection?
UPDATE
There is a new preFlush event (https://github.com/doctrine/doctrine2/pull/169) and I think this kind of things can be done there. That PR is not in the branch I'm using but let's try it!
When updating an entity during a listener's onFlush event, all you need to call is computeChangeSet():
// make changes to entity
$entity->field = 'value';
// or assign an existing entity to an assocation
$entity->user = $myExistingUserEntity;
$entity->tags->add($myExistingTagEntity);
$meta = $em->getClassMetadata(get_class($entity));
$uow->computeChangeSet($meta, $entity);
If you're creating other entities too, you need to persist them and compute their changes first!
$myNewUserEntity = new Entity\User;
$myNewTagEntity = new Entity\Tag;
$entity->user = $myNewUserEntity;
// make sure you call add() on the owning side for *ToMany associations
$entity->tags->add($myNewTagEntity);
$em->persist($myNewUserEntity);
$em->persist($myNewTagEntity);
$metaUser = $em->getClassMetadata(get_class($myNewUserEntity));
$uow->computeChangeSet($metaUser, $myNewUserEntity);
$metaTag = $em->getClassMetadata(get_class($myNewTagEntity));
$uow->computeChangeSet($metaTag, $myNewTagEntity);
$meta = $em->getClassMetadata(get_class($entity));
$uow->computeChangeSet($meta, $entity);
This can be done with the new preFlush event (Symfony 2.1).
Add a listener to the event (is a bad practice to inject the whole service container but sometimes is the way to go):
services:
mybundle.updater.listener:
class: Foo\MyBundle\Listener\UpdaterListener
arguments: ["#service_container"]
tags:
- { name: doctrine.event_listener, event: preFlush }
And the listener should be something like:
<?php
namespace Foo\MyBundle\Listener;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Foo\MyBundle\SomeInterface;
class UpdaterListener
{
/**
* #param \Doctrine\ORM\Event\PreFlushEventArgs $ea
*/
public function preFlush(PreFlushEventArgs $ea)
{
/* #var $em \Doctrine\ORM\EntityManager */
$em = $ea->getEntityManager();
/* #var $uow \Doctrine\ORM\UnitOfWork */
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if($entity instanceof SomeInterface) {
/*
* do your stuff here and don't worry because
* it'll execute before the flush
*/
}
}
}
}
When wanting to update the current entity you are sending to onFlush and also creating an association to that entity
(for this example I will use Parent object and child object)
Let's say when I change the parent object property 'stressed' to 1 I also want to associate a brand new child object to the parent object in my onflush method, it will look something like this:
public function onFlush(onFlushEventArgs $args)
{
....
$child = $this->createChild($em, $entity); // return the new object. just the object.
$uow->persist($child);
$childMeta = $em->getMetadataFactory()->getMetadataFor('AcmeFamilyTreeBundle:Child');
$uow->computeChangeSet($childMeta, $child)
$parent->setStressed(1);
$parentMeta = $em->getMetadataFactory()->getMetadataFor('AcmeFamilyTreeBundle:Parent');
$uow->recomputeSingleEntityChangeSet($parentMeta, $parent)
}
So there you see:
you need to persist your child object using $uow->persist() not $em->persist()
computeChangeSet on the child object.
recomputeSingleEntityChangeSet on the parent object
For help with creating the onFlush method, please see the documentation

Categories