ModelManagerException: Failed to create object - symfony

I'm using Sonata Admin bundle. When I submit a "Create entity" form the ModelManagerException is thrown if the form has some empty fields.
I've tracked this down to the PDOException that is thrown first. The exception is thrown because my empty fields get null values, but my table does not allow null values. I don't want to have null fields, I want empty string instead. How do I tell Sonata Admin class to create new entity with empty strings instead of nulls for fields without values?
This is the query that throws the exception:
INSERT INTO some_table (name, username, email) VALUES (?, ?, ?)' with params [null, "my name", "test#mailinator.com"]
These are the exceptions:
ModelManagerException: Failed to create object: Acme\DemoBundle\Entity\SomeEntity
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
I tried setting $this->name = ''; explicitly in SomeEntity constructor but it didn't help. The query still had null instead of ''. I tried overriding getNewInstance() on my Admin class to set name to '' value, but it didn't help either - query still had null instead.
The weird thing is that I can have empty fields on entity edit form - the entity updates fine and empty fields are saved correctly to the database. Why is this happening?
Example code:
<?php
namespace Acme\DemoBundle\Entity
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="some_table")
*/
class SomeEntity
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*
* We can't use Assert\NotNull()
* because Admin bundle uses null values for empty fields!
*/
protected $name;
// Other fields and methods.
}

you have two choices:
1- if you want the name field can be null, you need to edit the name annotation:
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $name;
2- if you want the name field be just an empty string when you create new instance, you just have to add the prePersist in your entityAdmin:
public function prePersist($object)
{
$object->setName('');
}
I hope it helps :)

There is some DBAL exception associated with this. Scroll lower on the symfony exception page and you'll see it.

Related

Sonata admin – blank page on production when ModelManagerException is thrown

I have defined unique constraint on one property of my entity.
/**
* #var string
* #ORM\Column(type="string", length=10, unique=true)
*/
protected $customID;
In Sonata admin, when new object is created with same ID, blank page on production is shown (in dev production, I can see that ModelManagerException is thrown, which is expected result).
How can I display an error on production?
Maybe use https://symfony.com/doc/current/reference/constraints/UniqueEntity.html for that field and it will show validation error before saving so no exception will be thrown.
Example:
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity("customID")
*/
class YourEntity {}

Doctrine: set foreign relation

This is regarding a problem with Doctrine when I try to insert a record into a associative entity. Below is a simplified description of the problem.
I have two tables, let's call them One and Two. Table One has a foreign key to table Two, called twoId with a column two_id. Field two_id happens to be part of the primary key.
* #ORM\Id
* #ORM\Column(name="user_id", type="string", length=40)
*/
private $twoId;
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
I am trying to insert a new record into table A. This works:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$one->setTwoId($two->getId());
$em->persist($one);
$em->flush();
I don't like to call both setTwo and setTwoId. Furthermore, I don't like reading the $two record before referencing it.
If I skip setTwoId call, I get the error: Entity of type [..] is missing an assigned ID for field 'twoId'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called.
If I skip setTwo call, I get the error: Integrity constraint violation: 1048 Column 'two_id' cannot be null
My problems are:
How can I avoid calling both setTwo() and setTwoId()?
What if I want to reference a entity from Two without reading it? Should I use $em->getReference()? (PhpStorm doesn't even recognize it)
In case someone makes the same mistake:
As pointed out by #lordrhodos, declaring the field $twoId was wrong because Doctrine will create it automatically without having a definition.
Definition:
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
Usage:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$em->persist($one);
$em->flush();

Deserialize and persist relationships with JMS Serializer

I'm trying to get the following working:
I've got an entity like:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Contact
*
* #ORM\Table()
* #ORM\Entity()
*/
class Contact
{
/**
* #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\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts")
* #ORM\JoinColumn(name="service_client", referencedColumnName="service_client")
*
* #JMS\Type("AppBundle\Entity\ServiceClient")
* #JMS\SerializedName("serviceClient")
*/
private $serviceClient;
}
I'm sending the following JSON over an HTTP request (Post, it's a new Contact, no ID):
{
"name": "Lorem Ipsum",
"serviceClient": {"service_client": "ipsum"}
}
What I expect is for the JMS Serializer to parse that relationship, and leting me persist the Contact object like this:
<?php
$contact = $this->get('serializer')->deserialize(
$request->getContent(),
Contact::class, 'json'
);
$this->em->persist($contact);
$this->em->flush();
In fact I got that working (I swear it was working) but now it's giving me the follwing error:
A new entity was found through the relationship
'AppBundle\Entity\Contact#serviceClient' that was not configured to
cascade persist operations for entity:
AppBundle\Entity\ServiceClient#000000006fafb93e00007f122bd10320. 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\ServiceClient#__toString()' to get a clue."
So it's tryign to persist the entity... a thing I do not want since the entity already exists. I just want Doctrine to put the reference, the foreign key.
Edit: It seems it's the constructor, if I set it to the doctrine_object_constructor it works like magic, the thing I do not understand is why it stop working in the first place.
Can anyone share any ideas or a cleaner way to do what I did?
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
This problem happens when Doctrine cannot map your relationship to an existing record in the database, so it will try to create a new one with the data from the JSON object.
In your case, the JSON object: {"service_client": "ipsum"} cannot be mapped to an existing ServiceClient instance.
It's because the default JMS object constructor call the unserialize function (will be the one from your Entity if you defined this method) to construct the object, which mean this object will always be treated by Doctrine as new (has never been persisted).
By using doctrine_object_constructor, JMS will get the object from Doctrine. The object came from Doctrine not only have the attributes and methods you define in your entity, but also meta-data about whether it's an existing one, it's corresponding row from the database ( so Doctrine can detect update made on the record later and handle it), therefore Doctrine are able to avoid incorrect persisting.
Doctrine will try to persist the Contact with a reference of a ServiceClient entity given in the deserialization. In the entity definition at the level of the manyToOne definition you need to add :
#ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts", cascade={"persist"})

Doctrine ManyToOne w/o a primary ID key

I'm trying to setup an FK between 2 entities in my DB. The parent table has a ManyToOne relationship to a child table. I can not join these tables using a normal parent_id => id FK due to how the child table is populated by external processes (the parent never knows the primary ID of the child).
Doctrine accepts the entities as shown below but Mysql fails when trying to add the FK to the table with the following error.
ALTER TABLE parent_tbl ADD CONSTRAINT FK_1172A832F85E0677 FOREIGN KEY (username) REFERENCES child_tbl (username);
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`dmname`.`#sql-f16_d8acf`, CONSTRAINT `FK_1172A832F85E0677` FOREIGN KEY (`username`) REFERENCES `child_tbl` (`username`))
class Parent
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Child")
* #ORM\JoinColumn(name="username", referencedColumnName="username", nullable=false)
*/
protected $username;
// ...
}
class Child
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
* #var integer
*/
protected $id;
/**
* #ORM\Column(type="string", unique=true)
*
* #var string
*/
protected $username;
// ....
}
From the Doctrine limitations and known issues :
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results.
Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
So, if you run the doctrine:schema:validate command, you should get something like :
[Mapping] FAIL - The entity-class Parent mapping is invalid:
* The referenced column name 'username' has to be a primary key column on the target entity class Child.
I hope you find a workaround to keep your logic intact using a primary key as join column.

doctrine2: undefined index - many-to-one with non default referencedColumnName does not persist entity

I'm using Symfony 2.1.2.
I have two entities and define a [many-to-one (bidirectional)] (1) association between them. I don't want to use the primary key for the foreign key (referencedColumnName). I want to use another integer unique column: customer_no
/**
* #ORM\Entity
* #ORM\Table(name="t_myuser")
*/
class MyUser extends BaseEntity // provides an id (pk)
{
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="user")
* #ORM\JoinColumn(name="customer_no", referencedColumnName="customer_no", nullable=false)
*/
public $customer;
}
/**
* #ORM\Entity
* #ORM\Table(name="t_customer")
*/
class Customer extends BaseEntity // provides an id (pk)
{
/**
* #ORM\Column(type="integer", unique=true, nullable=false)
*/
public $customer_no;
/**
* #ORM\OneToMany(targetEntity="MyUser", mappedBy="customer")
*/
public $user;
}
When I try to persist a MyUser entity with an Customer entity, I get this error:
Notice: Undefined index: customer_no in ...\vendor\doctrine\orm\lib\Doctrine\ORM\Persisters\BasicEntityPersister.php line 608
The schema on the db looks fine, these should be the important sql schema definitions:
CREATE UNIQUE INDEX UNIQ_B4905AC83CDDA96E ON t_customer (customer_no);
CREATE INDEX IDX_BB041B3B3CDDA96E ON t_myuser (customer_no);
ALTER TABLE t_myuser ADD CONSTRAINT FK_BB041B3B3CDDA96E FOREIGN KEY (customer_no)
REFERENCES t_customer (customer_no) NOT DEFERRABLE INITIALLY IMMEDIATE;
So there is definitely an index for customer_no
//update:
I fix the inversedBy and mappedBy stuff, but this is not the problem.
(1) : http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional
#m2mdas:
Yes you're right, I thought it's possible because JPA (which has influence to doctrine) has this feature. The attribute referencedColumnName only for the case when your property does not match the table column.
Whatever, I found a solution by patching the BasicEntityPersister.php, see here the gist on github: https://gist.github.com/3800132
the solution is to add the property/field name and value for the mapping column. This information is already there but not bound to the right place. It have to be added to the $newValId arrray this way:
$fieldName = $targetClass->getFieldName($targetColumn);
$newValId[$fieldName] = $targetClass->getFieldValue($newVal, $fieldName);
It only works for ManyToOne reference. ManyToMany doesn't work.
For ManyToOne I test it with already existing entities. You can test it, too:
change the doctrine annotation in tests/Doctrine/Tests/Models/Legacy/LegacyArticle.php
from
#JoinColumn(name="iUserId", referencedColumnName="iUserId")
to
#JoinColumn(name="username", referencedColumnName="sUsername")

Resources