A new entity was found through the relationship - forms

The error message:
A new entity was found through the relationship
'AppBundle\Entity\Tarifa#pesos' that was not configured to cascade
persist operations for entity:
AppBundle\Entity\TarifaPeso#0000000072d3bd4300000000232470d3. To solve
this issue: Either explicitly call EntityManager#persist() on this
unknown entity or configure cascade persist this association in the
mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot
find out which entity causes the problem implement
'AppBundle\Entity\TarifaPeso#__toString()' to get a clue.
Tarifa.php
/**
* #ORM\OneToMany(targetEntity="TarifaPeso", mappedBy="tarifa")
*/
private $pesos;
TarifaPeso.php
/**
* #ORM\ManyToOne(targetEntity="Tarifa", inversedBy="pesos", cascade={"persist"})
* #ORM\JoinColumn(name="tarifa_id", referencedColumnName="id")
*/
private $tarifa;
TarifaType.php
->add('pesos', CollectionType::class, array(
'entry_type' => TarifaPesoType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
))
The controller...
public function newAction(Request $request)
{
$tarifa = new Tarifa();
$form = $this->createForm('AppBundle\Form\TarifaType', $tarifa);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($tarifa);
$entityManager->flush();
$this->addFlash('success', 'project.created_successfully');
return $this->redirectToRoute('admin_post_index');
}
return $this->render('admin/tarifas/new.html.twig', array(
'form' => $form->createView(),
));
}
What am I missing? Really exhausted... please any help?

You should move cascade={"persist"} annotation from TarifaPeso::tarifa to Tarifa::pesos property. Or you can explicitly persist all pesos you get from the form:
$entityManager->persist($tarifa);
foreach ($tarifa->getPesos() as $peso) {
$entityManager->persist($peso);
}
$entityManager->flush();

Ok, it is solved, now it stores the Tarifa id in the TarifaPeso table. The error was I'd removed the line from the AddPeso method:
public function addPeso(\AppBundle\Entity\TarifaPeso $pesos)
{
$this->pesos[] = $pesos;
$pesos->setTarifa($this);
return $this;
}

Related

Form submission : value is set on null

I am making an ad platform, I have just created a Booking entity and its form, but after that the form has been submitted, the value 'amount' is set on null while it should not be null.
I have created a prePersist function to set the amount property before flushing.
Here is the prePersist function in my entity Booking
* #ORM\PrePersist
*
* #return void
*/
public function prePersist()
{
if(empty($this->createdAt))
{
$this->createdAt = new \DateTime();
}
if(empty($this->amount))
{
$this->amount = $this->ad->getPrice() * $this->getDuration();
}
}
public function getDuration()
{
$diff = $this->endDate->diff($this->startDate);
return $this->days;
}
My BookingController
/**
* #Route("/annonces/{id}/booking", name="ad_booking")
* #IsGranted("ROLE_USER")
*/
public function booking(Ad $ad, Request $request, ObjectManager $manager)
{
$booking = new Booking;
$form = $this->createForm(BookingType::class, $booking);
$user = $this->getUser();
$booking->setBooker($user)
->setAd($ad);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$manager->persist($booking);
$manager->flush();
return $this->redirectToRoute('booking_success', [
'id' => $booking->getId()
]);
}
return $this->render('booking/booking.html.twig', [
'ad' => $ad,
'bookingForm' => $form->createView()
]);
}
}
It does not work when the user is defined with $this->getUser(); in the submission and validity check. That's the first time it happens since I've started learning Symfony. I am sure I must have forgotten something but I spent so much time on thinking about what, that I can't see the answer.
and my BookingType form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('startDate', DateType::class, [
'label' => 'Date de début de prestatation',
'widget' => 'single_text'
])
->add('endDate', DateType::class, [
'label' => 'Date de fin de prestatation',
'widget' => 'single_text'
])
;
}
While the form is submitted, it should call the prePersist function and then set the amount property, but it returns to be null. I really don't understand what I missed.
Since it seems your PrePersist is not fired, my guess is you may have forgotten the #ORM\HasLifecycleCallbacks() annotation on your entity.
/**
* #ORM\Entity(repositoryClass="App\Repository\BookingRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Booking
{
...
}
I just found out that was wrong. It was linked to the automatic validation :
in the validator.yaml I commented the auto_mapping with
App\Entity\Booking: true
App\Entity\User: true
Now everything works fine !

Symfony CollectionType: Merge new entries

My symfony form represents an entity Mail which has a one to many relation with another entity called Attachment. Therefore, the MailType form contains a CollectionType field for embedding its AttachmentType forms:
$builder
->add('attachments', CollectionType::class, [
'entry_type' => AttachmentType::class,
'allow_add' => true,
'allow_delete' => false,
'by_reference' => false,
]);
My view will only send new attachments to my Symfony backend. So when storing the form data into the database, I only want to add new attachments of the mail and do not touch any existing attachments.
Unfortunately, Symfony / Doctrine behave differently: If n attachments are contained in the form data then n first existing attachments are overwritten by those new attachments:
existing attachments (in DB): [old1, old2, old3]
new attachments (contained by HTTP request): [new1, new2]
desired result in DB: [old1, old2, old3, new1, new2]
actual result in DB: [new1, new2, old3]
How can I achieve this? I thought by_reference => false will cause the addAttachment method to be called, so I also expected this to work out-of-the-box.
My Mail entity code:
class Mail {
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Attachment", mappedBy="mail", cascade={"persist", "remove"})
*/
protected $attachments;
...
public function addAttachment(\AppBundle\Entity\ttachment $attachment) {
$attachment->setMail($this);
$this->attachments[] = $attachment;
return $this;
}
}
My controller code processing the form:
// $mail = find mail in database
$form = $this->createForm(MailType::class, $mail);
$form->handleRequest($request);
if ($form->isValid()) {
$mail = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($mail);
$em->flush();
}
There are a couple of ways to do what you want. The simplest would be to give empty data or new attachment entities to your form field:
$builder
->add('attachments', CollectionType::class, [
'entry_type' => AttachmentType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'data' => [new Attachment()] // or add more or []
]);
Then in your Mail entity:
public function addAttachment(Attachment $attachment) {
$attachment->setMail($this);
$this->attachments[] = $attachment;
return $this;
}
public function removeAttachment(Attachment $attachment) {
return $this;
}
If you are using removeAttachment for some other functionality and you want to actually remove the Attachment, you can take advantage of the property_path settings of the form field:
'property_path' => 'appendAttachments'
and create addAppendAttachment and removeAppendAttachment:
public function addAppendAttachment(Attachment $attachment) {
$attachment->setMail($this);
$this->attachments[] = $attachment;
return $this;
}
public function removeAppendAttachment(Attachment $attachment) {
return $this;
}

Symfony forms -- removing an optional one-to-one relationship in an embedded form

I'm trying to use Symfony Forms to edit an existing entity that has a one-to-one relationship with another entity that is optional. I want to make it so that if it receives nothing for the associated entity, it will delete the entity.
Here's the gist of the code I've written:
class AType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('BType', new BType(), ['required' => false]);
}
}
The A entity's association with B:
class A
{
/**
* #ORM\OneToOne(
* targetEntity="B",
* mappedBy="a",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* #Assert\Valid()
*/
private $b;
....
When I try binding the form with the data, even though nothing was submitted for B, it'll make validation errors that B's fields are blank, because there is an existing B entity associated with A.
I've also looked at FormEvents, but I have not been able to find a way to fix the problem that way either -- example of what I've tried adding to AType's buildForm:
$builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
$a = $event->getData();
if($a->getType() != 'Package') {
$a->setB(null);
}
});
Even with this additional code, the errors regarding B are still appearing.
Update:
I think I've gotten closer, but though I can tell it's getting into the new code correctly, the B entity still doesn't ultimately get deleted, I think because despite the $a->setB(null), for $a = $form->getData(); in the controller, $a->getB(); still returns data (i.e. editing the entity in AType's POST_SUBMIT seems to only change that data locally?)
New Code:
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$data = $event->getData();
if(!isset($data['B'])) {
$event->getForm()->remove('B');
}
});
$builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
$a = $event->getData();
if(!$event->getForm()->has('B') && $a->getB()) {
$a->setB(null);
}
});
I'm really at a loss because while $a->setB(null) does not perpetuate to the controller, if I create a property $a->test and set that in the same location, that does perpetuate to the controller.
Update 2
As per comments, I'm adding some code from Entity B and the controller:
class B
{
/**
* #var \A
*
* #ORM\GeneratedValue(strategy="NONE")
* #ORM\OneToOne(targetEntity="A", inversedBy="b", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="a_id", referencedColumnName="id")
* })
*/
private $a;
As for the controller, it gets more complicated as I'm actually dealing with a 2 deep relation: C hasMany A hasOne B. It's also not using form inputs, but rather formatted json. I'm using http://jmsyst.com/bundles/JMSSerializerBundle/master/installation to serialize the output back into json, as well as a helper function to serialize any error messages.
//Where $c is either a new C entity or an existing C entity to be edited
private function handleForm($c)
{
$json = $this->getJsonFromRequest();
if (false === $json) {
throw new \Exception('Invalid JSON');
}
$form = $this->createForm(new CType(), $c);
$em = $this->getDoctrine()->getEntityManager();
if($form->bind(json_decode($json, true)) && $form->isValid()) {
$c = $form->getData();
...
$em->persist($c);
$em->flush();
$response = new Response($this->getSerializer()->serialize($c, 'json'));
$response->headers->set('Content-Type', 'application/json');
return $response;
} else {
$response = array('code' => 'invalid', 'details' => $this->getErrorMessages($form));
return new JsonResponse($response, 400);
}
}
class C
{
/**
* #ORM\OneToMany(targetEntity="A", mappedBy="c", cascade={"persist", "remove"}, indexBy="id")
* #Assert\Valid()
* #Serializer\Expose
*/
protected $a;
A's relationship to C
/**
* #var \C
*
* #ORM\ManyToOne(targetEntity="C", inversedBy="a")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="c_id", referencedColumnName="id")
* })
* #Assert\NotBlank
*/
private $c;
And CType
class CType extends AbstractType
{
$builder->add(
'a',
'collection',
array(
'type' => new AType(),
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true
)
);

Sonata admin don't save the many-to-many field

This is the common porblem.
There is the form with many checkbocks. Making the checkbocks are aviable and click save, fields corresponding checkbock-labels don't save.
PersonAdmin class contains
...
->add('books', 'sonata_type_model',
array('by_reference' => false, 'expanded' => true, 'multiple' => true, 'label' => 'Books'))
...
Entity class contains
/**
* #ORM\ManyToMany(targetEntity="Book", mappedBy="persons", cascade={"persist"})
* #ORM\JoinTable(name="person_book")
*/
protected $books;
....
public function __construct()
{
$this->books = new ArrayCollection();
}
public function addBook(Book $book)
{
$this->books[] = $book;
return $this;
}
and geters, seters...
I unsuccessfully searched for a solution. I have found that it is necessary to add
'by_reference' => false,
or
cascade={"persist"}
but I have all of this in my code.
As mentioned before you need to save relation in both sides. But I would prefer another way: to save relation in the add actions of your entities:
//In the Person entity:
public function addBook(Book $book)
{
$book->addPerson($this);
$this->books[] = $book;
return $this;
}
//In the Book entity (if you have the same problem for another side):
public function addPerson(Person $person)
{
$person->addBook($this);
$this->persons[] = $person;
return $this;
}
You have to save at both sides.
Override the default editAction and createAction in a custom crud controller.
e.g.:
It's an example of a many to many relationship between artists and events.
($object is the current object you are editing/creating in the action)
foreach ($form['selectArtists']->getData() as $key => $value) {
$artist = $em->getRepository('MyCompanyProjectBundle:Artist')->findOneById($value);
$object->addArtist($artist);
$artist->addEvent($object);
$em->persist($artist);
}
Use edit=>inline .
->add('books', 'sonata_type_collection',
array('by_reference' => false, 'label' => 'Books'),
array('edit'=>'inline','inline'=>'table'))
May be help you.

Symfony forms. File upload

Trying to manage file upload with Entity, but i get this error:
Fatal error: Call to a member function move() on a non-object in /home/projectpath/src/BS/MyBundle/Entity/Items.php on line 327 Call Stack: 0.0002 333264 1. {main}() /home/projectpath/web/app_dev.php:0 0.0450 1158160...
Here's the entity class:
namespace BS\BackstretcherBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* MB\MyBundle\Entity\Items
*
* #ORM\Table(name="items")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Items
{
private $filenameForRemove;
/**
* #Assert\File(maxSize="60000000")
*/
public $file;
...
protected function getUploadDir()
{
return 'images/items/';
}
protected function getUploadRootDir()
{
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
public function getWebPath()
{
return null === $this->file ? null : $this->getUploadDir().'/'.$this->getNameEn();
}
public function getAbsolutePath()
{
return null === $this->file ? null : $this->getUploadRootDir().'/'.$this->getNameEn().'.jpg';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file)
{
$this->file = $this->getId() .'.'. $this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file)
{
return;
}
$this->file->move($this->getUploadRootDir(), $this->file);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath())
{
unlink($file);
}
}
And the controller:
public function new_productAction(Request $request)
{
$product = new Items();
$product->setPrice(0);
$form = $this->createFormBuilder($product)
->add('Type', 'choice', array(
'choices' => array('1' => 'Product', '0' => 'Article'),
'required' => false,))
->add('Price', 'number')
->add('nameEn', 'text')
->add('file', 'file', array('label' => 'Image', 'required' => true))
->getForm();
if ($request->getMethod() == 'POST')
{
if ($form->isValid())
{
$form->bindRequest($request);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($product);
$em->flush();
return new Response('<html><body>Success!</body></html>');
}
}
return $this->render('MyBundle:Default:admin_page.html.twig', array(
'form' => $form->createView(),
));
}
Symfony version: 2.1.0
Check your php.ini file and make sure both the post_max_size AND upload_max_filesize are set sufficiently large.
I don't suppose duke_nukem is worried about this anymore, 6 months down the line, but if someone else comes across this question, I was having the exact same problem and got a great answer to it here:
Error with file upload in symfony 2
Looks like duke_nukem and I made the same mistake. The preUpload() method should read:
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file)
{
$this->path = $this->getId() .'.'. $this->file->guessExtension();
}
}
The present code converts $this->file to a string, causing the error. The path should actually be assigned to $this->path.
Sybio in the other question figured this out, not me. I just want to spread the love.
it's weird
your code is wrong in your controller. You have to bind your request to your form before validation. After that, you can retrieve your data
if ($request->getMethod() == 'POST')
{
//Note: bindRequest is now deprecated
$form->bind($request);
if ($form->isValid())
{
//retrieve your model hydrated with your form values
$product = $form->getData();
//has upload file ?
if($product->getFile() instanceof UploadedFile){
//you can do your upload logic here wihtout doctrine event if you want
}
$em = $this->getDoctrine()->getEntityManager();
$em->persist($product);
$em->flush();
return new Response('<html><body>Success!</body></html>');
}
}