Inserting data with Doctrine - Repository Vs Entity - php

I've realized there is a difference between a Doctrine repository and Doctrine entity.
I'm trying to implement simple CRUD actions on a table and was injecting a default Doctrine repository into my controller (without injecting an entity).
For the "Update" action I would first ->find($id) for the record to update and it would return an instance of the entity for me to bind to my form object.
For the "Create" action I realized I can't ->find($id) a record to insert (since it doesn't exist) in order to retrieve an instance of the entity for me to bind to my form object.
Is there is an alternate way to insert data using Doctrine without an instance of an entity? Or is there a way to retrieve an instance of the entity from the repository so I can ->bind() it to the form? If the answer to both are no, then I imagine my only options are to inject an instance of the entity to my controller, or to use a custom repository which contains a method which would return an entity to use in the ->bind() for insertion.
My guess would be to define a custom repository which has a method which retrieves an empty entity instance for use in insertion. Is this assumption correct?

As pointed by #Crisp in comments, Entities are no more than PHP classes, same for Repositories.
The two are differentiated by their respective role.
You'll never implicitly create a new instance of a Repository because doctrine do it for you across DependencyInjection principles (Service, Factory, ...).
To create a new database entry, you must create a new instance of the corresponding entity, then store it using EntityManager::persist and EntityManager::flush methods.
Reuse the same instance of an entity would not give you any benefit, nor make any difference in your project's maintainability.
The entity class itself will never be broken/changed, only instances of them are created, renamed, moved, deleted.
These instances represents your database entries, this is the primary interest of use an ORM.

Related

Unique error persisting entity updates to db with Doctrine

I'm using Doctrine in my Symfony project to manage the persisting layer in my application.
Recently, I've been having some issues when persisting changes from my entities to my database. The problem I've been having is that when I update an entity and save it to my database, sometimes the EntityManager treats my entity as a new object, so instead of performing an update operation, it performs an insert operation, thus causing a unique exception error in my database.
As the docs say, when updating an object you should only perform these steps:
fetch the object from Doctrine
modify the object
(optional) call persist() on the entity manager
call flush() on the entity manager
Note I added (optional) to the persist() call because, as the docs say, it isn't necessary since Doctrine is already watching the object for changes
Now that things are explained, this is the work I do in my code:
$myEntity = $this->myEntityRepository->byId($id);
// make some changes to the entity
$myEntity->setSomething('something');
$this->myEntityRepository->save($entity);
Where the save() operation in my repository is as follows:
$this->entityManager->persist($entity);
$this->entityManager->flush();
And the byId() operation:
return $this->entityManager->getRepository()->find($id);
As I said, the persist operation should only be called when persisting new entities, but since Doctrine can differentiate between an already managed entity and a new one, it should be no problem. If I didn't call the persist() method, instead of executing and insert operation and cause a unique violation, it would literally do nothing as it wouldn't detect any changes to my operations.
The reason I always use the persist() method is because the save() operation in my repository is used with both new entities and updates to existing entities.
As I've seen in another answer, calling the merge() operation instead of persist() should solve the problem, but I don't see it right because I think it's just a "dirty" solution, plus the method is being deprecated in future versions of Doctrine.
So, what am I missing here? Why sometimes I get a unique error when running the above code? I only have one connection and one entity manager configured in my application.
I'd like to add that the only occurrences of this problem are found in code executed in consumers (async events), not in the API itself, but whenever I receive a new event, a new and fresh connection to the database is created to ensure I don't have overlapping problems with the entity manager used in some previous event.
When talking about consumers I mean that an event is published via RabbitMQ (the event contains the ID of the entity) and then it gets consumed in a separate process from the API, fetching the entity directly with the entity manager repository.
My guess is that between the line where I get my entity from the repository (i.e. I use the find() method) and the line where I save it into the database (i.e. I use the flush() method), the entity manager somehow removes the entity from its UnitOfWork so it treats it as a new entity instead of a managed one.
An issue I recently found in some code was where an entity was being passed - via a RabbitMq message and the entity created from scratch, including the id -- $x = new Entity(); $x->setId($data['id');$x->setName($data['name'); # ... etc).
Having an id set does not make the entity 'managed' by Doctrine however - only reading the original record (from the DB) and then updating it. Persisting $x as above will create a new record - and ignoring the id that was set.
I would ensure (perhaps with some assertions on the record that you are fetching that must pass - or die with an error, at least for now) so that know that you are always dealing with a known entity (I'm also presuming that your ->byId($id) call is just a ->find($id) internally).

How to perist only the first level of an entities hierarchy with Doctrine?

I'm migrating the DBAL of a ZF3 application to Doctrine and want to go ahead step by step. Currently I'm using a hierarchy of Mapper objects. Each entity in the like FooEntity hierarchy has an according FooMapper. Saving of nested entities is performed by nested Mappers. Every Mappers saves its entity with Zend\Db\Sql\Insert or Zend\Db\Sql\Update and calls the proper Mappers for the sub-entities like BarMapper for BarEntity.
Now, before I start with Doctrine's convenience features like cascade={"persist"}, I want to keep the Mapper's hierarchy and just to perform the saving of the top level of the nested entity with persist(...) & flush().
But when I try it
public function save(AbstractDataObject $dataObject)
{
$newLogicalConnection = $this->logicalConnectionMapper->save($dataObject->getLogicalConnection());
$newUser = $this->userMapper->save($dataObject->getUser());
$dataObject->setLogicalConnection($this->entityManager->find(LogicalConnection::class, $newLogicalConnection->getId()));
$dataObject->setUser($this->entityManager->find(User::class, $newUser->getId()));
$this->entityManager->persist($dataObject);
$this->entityManager->flush();
return $dataObject;
}
I get an error
A new entity was found through the relationship 'MyNamespace\DataObject\AbstractEndpoint#externalServer' that was not configured to cascade persist operations for entity: MyNamespace\DataObject\ExternalServer#000000006098ccff0000000068c23676. 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 'MyNamespace\DataObject\ExternalServer#__toString()' to get a clue.
So, Doctrine seems to try saving the whole entity with its sub-entities, and this attempt fails on one of the lower levels. But why? I have not activated any cascade options and expect Doctrine to save only the top level.
Why does Doctrine try to save the whole entity and not only the top level? And how to get it saving only the top level of the given entity?
You get this error because you have a new entity (not persisted yet) in AbstractEndpoint->externalServer and this field is not annotated as cascade={"persist"}
In other words you have just created a new entity ExternalServer and did not persisted it and added it as a relation to AbstractEndpoint->externalServer entity which is not annotated as cascade={"persist"}
So Doctrine ends up having this new entity and does not know what to do with it. In order not to lost any data this exception is raised.
To fix this you can do two things:
Add $this->entityManager->persist($externalServer); right after you create ExternalServer entity
Annotate AbstractEndpoint->externalServer with cascade={"persist"}. Which you don't want to do because you want only top level entity to be saved to DB so you need to persist it manually or DO NOT add it is a relation.
And now answering your question:
But why? I have not activated any cascade options and expect Doctrine to save only the top level.
Somehow through relations in your object model Doctrine goes down to ExternalServer entity and finds it in unpersisted state. You can not save only top level of object hierarchy with link to unexisting record in relational database. If you do not want Doctrine to do it for you - you must handle this situations by yourself or remove not persisted entities from relations

How to use Repository without associate to an Entity class in symfony 2.8

I would like logic like the following:
-> controller- > call to Model class (that extend model with the entity,manager)- > the function in model will call to getRepository() and use funcion that dealing with the DB.
How can I do that? When I call to Repository I must have an entity, and if I have an empty entity the error is that I must have primary column with id.
As the first comment says, you should not call the repository directly from the entity class. You have several options here (descending by the code quality)
Refine the logic to invert the control flow
Define a service, which accepts the entity, the repository and does the stuff
$service = new EntityStuffService($repository);
$service->doTheStuff($entity);
Declare a relation
Just declare a realtion within your class and filter it manually. Doctrine PersistentCollection implements Selectable, so you can just filter it using the ->matching($criteria) call.
http://doctrine-orm.readthedocs.io/en/latest/reference/working-with-associations.html#filtering-collections
The ugliest way
Doctrine has the ObjectManagerAware interface, which allows you to inject the object manager into the entity on hydation completion. This will allow you to implement and kind of AR patters and other spaghetti-code and I strongly recommend you not to do this.
https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Persistence/ObjectManagerAware.php
You can implement this interface and store the ObjectManager inside the entity to do any kind of operations with it.

Doctrine creates new row when editing entity from cache

I have retrieved a simple (no joins) serialised entity from a Redis cache.
When I edit this entity and call persist() and flush() on the entity manager, a new row is inserted instead of updating the existing entity.
The Doctrine documentation only seems to mention calling merge() with the entity but that fetches the item from the database which defeats the purpose of caching it.
How can I make the entity manager track this restored entity?
Thanks
Unfortunately, you do need to call merge() - this is just so that the EntityManager is properly aware of the entity. If you serialise the entity and then load it back up in a new session then the EntityManager now only knows the type and the attributes - it doesn't know if it has been previously saved to the database etc, which is why it just assumes it is new and creates a new row unless merged.
The EntityManager has an internal list of what's been saved, what's pending etc; it needs all this info to correctly do it's job - these details are not stored in the entities themselves. An unserialised entity read from cache is considered "unmanaged".
From the Doctrine docs on this:
In Doctrine an entity objects has to be “managed” by an EntityManager
to be updateable. Entities saved into the session are not managed in
the next request anymore. This means that you have to register these
entities with an EntityManager again if you want to change them or use
them as part of references between other entities. You can achieve
this by calling EntityManager#merge().
It doesn't completely defeat the purpose of caching it, because really you only need to merge at the point that you want to save back to the database.

Is it possible to have Symfony and/or Doctrine hydrate associated objects managed by different entity managers?

I have a legacy application that was using Xaraya to manage user content that I am trying to replace with a rewrite using Symfony/Sonata to manage users and/or content.
For whatever reason, previous developers managed this with two different databases (MySQL for Xaraya, and SQL Server for other things, including authenticating users).
I am trying to create Entity mappings such that the users/groups from SonataUserBundle (which extends FOSUserBundle) use the entity manager associated with the login database connection, and this works for logging into the admin site itself, but blows up when it tries to hydrate objects that have associations to the User entity.
It appears that Doctrine does not try to find the entity manager associated with an entity when hydrating an object's associations.
My question is this: it it possible to make Doctrine hydrate objects using the entity manager for an entity instead of assuming it's mapped to the current entity manager, and if not, is there any form of a clean code work-around for it?
Thanks.
(Note: The method of using the "databasename.tablename" syntax in the query that I have seen mentioned elsewhere will not work for my use case.)

Categories