Doctrine 2.5. OneToOne relationship sharing primary key - symfony

In my app, when users sign up I have to send them an email with a validation key, as usually happens on most websites, I'm trying to do this with Doctrine but I can’t get it to work when I try to persist() the user.
First of all, I think the correct way in this case is to use a OneToOne unidirectional relationship, but I don’t know if it would be better to use a bidirectional one. I've tried both and I always get an error.
I have read these two questions carefully:
One to one relationship on two tables sharing primary key
Doctrine one-to-one unidirectional
As well as this part of the documentation. When I validate the schema (php bin/console doctrine:schema:validate) everything is fine.
class Usuario {
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ...
}
class ClaveVal {
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Usuario")
*/
private $usuario;
/**
* #ORM\Column(name="clave", type="string", length=20, nullable=false)
* #Assert\NotBlank()
*/
private $clave;
// ...
}
Which is quite similar to this.
Now, I'm trying to persist() a new Usuario and a new ClaveVal for this usuario like this:
$usuario = new Usuario();
// Add usuario attributes
$claveVal = new ClaveVal();
$claveVal->setUsuario($usuario);
$claveVal->setClave(‘123456’);
$em->persist($usuario);
$em->persist($claveVal);
But I get this error:
The given entity of type 'AppBundle\Entity\ClaveVal'
(AppBundle\Entity\ClaveVal#000000007020f28f0000000031d5c8c6) has no
identity/no id values set. It cannot be added to the identity map
I know why this happens. This works perfectly:
$em->persist($usuario);
$em->flush();
$em->persist($claveVal);
$em->flush();
But I don't want to do that because I want it to be a unit of work using flush() only once.
Besides, as the author of the post I linked above says, it should be Doctrine's job to flush() at the right moment to get the id.
So, how can I achieve this using flush() only once (and without using transactions or listeners, I'm sure there is an easier way to do this)? Would it be better to use a bidirectional OneToOne relationship? As I said, I tried it too but I got the same error.
Thanks in advance.

Related

Doctrine throw error: "A new entity was found through the relationship" after removing entity

Let's say I have two entities, Project and User with relation.
Project.php
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(onDelete="SET NULL")
*/
private $creator;
When I remove the User entity, the doctrine leaves the User object(without ID) in the Project entity. In a normal situation, this is fine but I am using DomainEvents. In this scenario, after removing the User entity, DomainEvent triggers saving some data in the DB and secondary saving data(after removing) throw this error. This happens because of now in the Project entity we have the detached(from the EM) User object without ID.
I thought about a listener, that will remove empty objects in the entity after removing, but I am not sure that is a good variant
What is the best variant for solving this error?
The onDelete option doesn't apply a cascade removing.
If you want to do so I think you should have to add the cascade={"remove"} option to the ManyToOne.
Try as following :
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", cascade={"remove"})
* #ORM\JoinColumn(onDelete="SET NULL")
*/
private $creator;
Removing entity in doctrine

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"})

Select with where clause that uses a field that is not in index returns: Binding an entity with a composite primary key to a query is not supported

I have a table with a composite primary key:
class tablea {
/**
* #ORM\Column(type="datetime")
* #ORM\id
*/
protected $a;
/**
* #ORM\Column(type="integer")
* #ORM\id
*/
protected $b;
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
protected $c;
/**
* #ORM\Column(type="integer")
*/
protected $d;
/**
* #ORM\Column(type="integer")
*/
protected $e;
}
When I try to execute a query to return all record with d=100 (for instance) I allways get a error message:
Binding an entity with a composite primary key to a query is not
supported. You should split the parameter into the explicit fields and
bind them separately.
here is my code:
$_qry = $_rep->createQueryBuilder('m')
->Where("m.d = :ini")
->setParameter('ini', 100)
->getQuery();
But, if I change to a field that is an index or if I remove the where clause, the query works fine.
I'm not sure how to fix your exact error, but you may want to look into the limitations Doctrine has with composite keys, specifically the restriction on only having primitive types - in your example you're using a datetime as an ID. It could be that this is causing the problem.
While not a solution, this is one of those instances where trying to make Doctrine play nice is going to be more hassle than either:
just adding a synthetic generated key to the entity
using native SQL / a different ORM solution (e.g. Propel)
The impact of either of these choices is obviously dependent on your project - I've found the former the easiest way to go about things, however if you're porting an existing project or you expect the table to be massive (or you're just really picky over normalisation) then this may not be the way to go.
In short: composite keys in Doctrine are a short path to pain.

Doctrine Associations Mapping

I'm trying Doctrine Associations with Symfony2 for the first time and it's giving me headache.
I have an Admin interface that can, among other things, upload images. I want to know which administrator uploaded what image so i've putted a foreign key administrator to my images table. To gather data, a simple JOIN is necessary to collect the data but with Doctrine, I'm stuck, altough it seems simple.
So, I have an Administrator object that reflects the table. In that object, I have this statement...
#ORM\OneToMany(targetEntity="ImageBundleNamespace\ImageEntity", mappedBy="administrator")
It's simple. In my ImageEntity object (that reflects Images table) is a foreigh key column administrator.
In the ImageEntity object, I use this statement...
#ORM\ManyToOne(targetEntity="AdministratorNamespace\Administrator", inversedBy="imageEntity")
#ORM\JoinColumn(name="administrator", referencedColumnName="id")
There is a administrator field in ImageEntity and a imageEntity field in Administrator that the formentioned statements are mapping.
It doesn't work.
I've run the SchemaValidator on the EntityManger and it says the the administrator field on the ImageEntity object is not defined as an association. The second message says that the administrator field does not exist.
If it helps, this is my DQL for all of it...
'SELECT i.id,
i.imeSlike,
i.velicina,
i.ekstenzija,
i.paths,
a.username,
a.ime,
a.prezime FROM ImageBundle:ImageEntity i
JOIN a.administrator a'
Thank you in advance for all the help.
EDIT
I had a mistake in DQL. Corrected it.
EDIT
I forgot to add the source code.
Association part of the Administrator...
**
* #ORM\OneToMany(targetEntity="Icoo\Administracija\GalerijaBundle\Entity\ImageEntity", mappedBy="administrator")
*/
protected $imageEntity;
public function __construct() {
$this->imageEntity = new ArrayCollection();
}
Association part of the ImageEntity
/**
* #ORM\Column(type="smallint")
*
* #ORM\ManyToOne(targetEntity="Icoo\LoginBundle\Entity\Administrator", inversedBy="imageEntity")
* #ORM\JoinColumn(name="administrator", referencedColumnName="id")
*
*/
protected $administrator;
In the administrator class, you have:
/**
* #ORM\Column(type="smallint")
*
* #ORM\ManyToOne(targetEntity="Icoo\LoginBundle\Entity\Administrator", inversedBy="imageEntity")
* #ORM\JoinColumn(name="administrator", referencedColumnName="id")
*
*/
you need to remove the #ORM\Column(type="smallint")
This must be all. Let me know.
You can separate field mapping from association mapping:
/**
* #ORM\Column(name="administrator", type="smallint", nullable=false, options={"unsigned"=true})
*/
protected $administratorId;
/**
* #ORM\ManyToOne(targetEntity="Icoo\LoginBundle\Entity\Administrator", inversedBy="imageEntity")
* #ORM\JoinColumn(name="administrator", referencedColumnName="id")
**/
protected $administrator;
If you go further you can put exception into $administratorId getter/setter to avoid usage of it.
As i have tested, doctrine ignores value into $administratorId property when you persist/flush your entity (For confirmation you can look at prepareUpdateData() in Doctrine\ORM\Persisters\BasicEntityPersister)
EDIT
I think, my previous variant is possible but wrong. Because, doctrine gets field mapping from definitions in referencedColumn, you can add some more definitions using
* #ORM\JoinColumn(name="administrator", referencedColumnName="id", unique, nullable, onDelete, columnDefinition, fieldName)
This means that your image_entity.administrator field in db will be the same as your administrator.id field in db (except of additional definitions)

Symfony: How do I annotate entity properties that are objects to get Doctrine to store a foreign key?

I'm still getting to grips with Symfony and Doctine and I appreciate this might sound overly simple.
I have at present two basic entities: WebSite (having id and canonicalUrl properties) and Job which has, as one property, a WebSite.
A Job has one WebSite; a WebSite can be referenced by many Jobs. Both are under the same namespace.
Relevant here is the Job entity:
/**
*
* #ORM\Entity
*/
class Job
{
/**
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*
* #var WebSite
*/
protected $website;
}
In database terms, a persisted Job should be storing the id of the relevant WebSite.
Without any changes to the above, calling php app/console doctrine:migrations:diff generates a new migration for a table named Job with a single id field.
How do I annotate Job::website such that Doctrine knows to create an integer field and to get the value as the id of the Website object?
You must explicitly define the relationship. The shortest would be
/**
* #ORM\Entity
*/
class Job
{
/**
* #var WebSite
*
* #ORM\ManyToOne(targetEntity="Website")
*/
protected $website;
}
However, should you find yourself wanting to tweak the relationship to better suit your needs, have a look at the annotation reference (ManyToOne and JoinColumn for this particular case). There's also quite a comprehensive article about association mapping, which you might find interesting.

Resources