When saving form submission data, I'm having trouble persisting a new entity instance where the entity has a nullable association with another entity and I attempt to set it to null. After creating a new entity instance for the form, binding the submitted request to the form and persisting and flushing the entity instance, depending on how I populate the property for the associated entity, I either get
UnexpectedTypeException: Expected argument of type "object or array", "NULL" given
(if set to null), orInvalidArgumentException: A new entity was found through the relationship 'AccessLog#document' that was not configured to cascade persist operations for entity
(if set to a new, empty instance of the related entity, which I don't want to persist).If I set up cascade persist, it tries to create a record in the related table (which is not allowed by the data model in the db), even though there's no data for it to persist. If setting up cascade persist is the way to go, how do I prevent it from trying to create a new record? What's the best way to handle this?
Note, the behavior is the same whether the association is set up as unidirectional or bidirectional.
Details:
I have an entity with a many-to-one association with another entity (abbreviated):
/** @Entity */
class AccessLog
{
/** @Id @Column(type="integer") */
private $access_log_id;
/** @Column(type="integer", nullable=true) */
private $document_id;
/**
* @ManyToOne(targetEntity="Document", inversedBy="access_logs", cascade={"persist"})
* @JoinColumn(name="document_id", referencedColumnName="document_id")
*/
private $document;
// plus other fields
// plus getters and setters for all of the above...
}
The related entity is nothing fancy:
/** @Entity */
class Document
{
/** @Id @Column(type="integer") */
private $document_id;
/** @Column(length=255) */
private $name;
/** @OneToMany(targetEntity="AccessLog", mappedBy="document") */
private $access_logs;
// plus other fields
// plus getters and setters for all of the above...
}
I have a Symfony form for entering data for new AccessLog records:
class AccessLogFormType extends AbstractType
{
public function getName()
{
return 'access_log_form';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'AccessLog');
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('access_log_id', 'hidden');
$builder->add('document_id', 'hidden', array(
'required' => false
));
$builder->add('document', new DocumentType(), array(
'label' => 'Document',
'required' => false
));
//...
}
}
With the following supporting type definition:
class DocumentType extends AbstractType
{
public function getName()
{
return 'document';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Document');
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name', 'text', array(
'required' => false
));
}
}
My controller includes the following:
public function save_access_log_action()
{
$request = $this->get('request');
$em = $this->get('doctrine.orm')->getEntityManager();
$access_log = null;
if ($request->getMethod() === 'POST') {
$data = $request->request->get('access_log_form');
if (is_numeric($data['access_log_id'])) {
$access_log = $em->find('AccessLog', $data['access_log_id']);
} else {
$access_log = new AccessLog();
}
if (is_numeric($data['document_id'])) {
$document = $em->find('Document', $data['document_id']);
$access_log->set_document($document);
} else {
// Not calling set_document() since there shouldn't be a
// related Document entity.
}
$form = $this->get('form.factory')
->createBuilder(new AccessLogFormType(), $access_log)
->getForm();
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($access_log);
$em->flush();
}
} else {
// ... (handle get request)
}
return $this->render('access_log_form.tpl', array(
'form' => $form->createView()
));
}
The above code works fine when updating an existing access log entry or creating a new one and a document is selected in the form, but not if no document is selected.
Assuming the data model cannot be changed, how do I go about persisting a new AccessLog entity without persisting a new Document entity?
It appears that the solution was to move the set_document(null) call to right before the persist operation, since binding the request to the form caused an empty Document object to be attached to the $document property of the AccessLog object.
This solution also works after removing the cascade persist and making the association unidirectional.
Thanks to @greg0ire for his help.
[update] It also became necessary to add @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
to the Document entity definition as it continued to try to update the Document record associated with the AccessLog record, for example when removing an existing association (which caused the $name property to be set to null on the Document and then in the db). It's really frustrating that the default behavior is to erase/modify data when an association is removed, even when cascade persist is not specified.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With