Skip persisting of a related entity in Doctrine2 - php

I have an entity "Entity1", that have an OneToOne (one-direction) relation with another entity "Entity2". Entity2 is managed by a different EntityManager.
Here is part of the Entity1:
/**
* #ORM\OneToOne(targetEntity="Entity2")
* #ORM\JoinColumn(name="Entity2ID", referencedColumnName="ID")
**/
protected $entity2;
Entity2 already exists, Entity1 is a new entity. Here is part of the persisting logic:
$obj1 = new Entity1();
$obj1->setEntity2($this->entity2Manager
->getRepository('VendorBunlde:Entity2')
->find($entity2Id));
$this->entity1Manager->persist($obj1);
$this->entity1Manager->flush();
I got this error:
A new entity was found through the relationship 'MyBundle\\Entity\\Entity1#entity2' that was not configured to cascade persist operations for entity: VendorBundle\\Entity\\Entity2#0000000008fbb41600007f3bc3681401. 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 'VendorBundle\\Entity\\Entity2#__toString()' to get a clue
How can I force Doctrine to skip persisting Entity2 and in the same time keep the relation? I tried "->merge($obj1)" it works but I get null when I call $obj1->getId()?!

I would try the detach method:
An entity is detached from an EntityManager and thus no longer managed
by invoking the EntityManager#detach($entity) method on it or by
cascading the detach operation to it. Changes made to the detached
entity, if any (including removal of the entity), will not be
synchronized to the database after the entity has been detached.
$obj1 = new Entity1();
$obj2 = $this->entity2Manager
->getRepository('VendorBunlde:Entity2')
->find($entity2Id);
$obj1->setEntity2($obj2);
$this->entity1Manager->detach($obj2);
$this->entity1Manager->persist($obj1);
$this->entity1Manager->flush();
Haven't tried though, please let me know if it works, thanks.

This solution works for me: Using Relationships with Multiple Entity Managers
I removed the relationship between entity1 & entity2.
Added #PostLoad to load entity2.

Related

Doctrine persist not updating existing records but trying to create a new one

In my application my domain model (aggregate) is different than a database models (entities), and because of that I have a separate aggregate and doctrine entity classes, and a mapper class which is building doctrine entity from aggregate and vise versa before getting/persisting, I'm doing it's directly in repository, like this:
public function persist(Aggregate $aggregate): void
{
$this->entityManager->persist(
$this->mapper->mapToDoctrineEntity($aggregate)
);
}
public function get(AggregateId $id): Aggregate
{
/**
* #var ?DoctrineEntity $entity
*/
$entity = $this->entityManager->find(DoctrineEntity::class, $id->toInt());
if (null === $entity) {
throw new AggregateNotFoundException(sprintf('Aggregate [%d] not found', $id->toInt()));
}
return $this->mapper->mapToAggregate($entity);
}
What mapper doing is simply creating new class of doctrine enitty or aggregate depends on what we need to create and populate with a data from entity/aggregate. All working fine with creating new records, but when I'm trying to update existing, doctrine does not see that entity with provided id already in the database and trying to insert it, which causing a duplication error. Is there a way to notify doctrine somehow that it's existing record????
Btw, before updating records I'm using repo method get to get existing record from database, but probably due to that transformation from doctrine entity to aggregate, doctrine loose tracking of that entity
Custom mapper on top of the doctrine seems not a good idea. Doctrine already is a data mapper and entities is meant to be a domain model.
But if you really need it you must keep entity that you got from repository, and update it on changes.

Merge is creating new record for children

Merge is creating not working for children #OneToMany
I am using Php Doctrine and I am using #OnToMany mapping with cascade all. I have a parent class SalesOrder and child class SalesOrderDetails.
Case 1 : Save - When I save new record sales order along with sales order details. It is working as expected.
Case 2 : Update - Here is the issue, I am merging the Sales Order which is fine however its inserting new records for its children SalesOrderDetail instead of updating it. Ideally it should it apply mergebut for children as well but its not.
As of now, I am getting the Sales Order Details by id from DB then change the properties of it. Ideally that should not be the case, mean if we set the id to unmanned object, it should update instead of creating new records.
Note:
1. Merge is working with parent object if it has the id value.
2. I am not adding new item here, I am just updating the existing recorded through merge.
SalesOrder.php
/**
* #Entity #Table(name="sales_orders")
* */
class SalesOrder extends BaseEntity {
/**
* #OneToMany(targetEntity="SalesOrderDetail",cascade="all", mappedBy="salesOrder" )
*/
protected $itemSet;
function __construct() {
$this->itemSet = new ArrayCollection();
}
}
SalesOrderDetail.php
/**
* #Entity #Table(name="sales_order_details")
* */
class SalesOrderDetail extends BaseEntity {
/** #Id #Column(type="integer") #GeneratedValue * */
protected $id;
/**
* #ManyToOne(targetEntity="SalesOrder")
* #JoinColumn(name="order_no", referencedColumnName="order_no")
*/
protected $salesOrder;
}
Debug Mode screen
If I use cascade={"merge"}
I am getting different error if I am using Cascades merge
Type: Doctrine\ORM\ORMInvalidArgumentException Message: Multiple
non-persisted new entities were found through the given association
graph: * A new entity was found through the relationship
'Ziletech\Database\Entity\SalesOrder#itemSet' that was not configured
to cascade persist operations for entity:
Ziletech\Database\Entity\SalesOrderDetail#0000000052218380000000007058b4a6.
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
'Ziletech\Database\Entity\SalesOrderDetail#__toString()' to get a
clue. * A new entity was found through the relationship
'Ziletech\Database\Entity\SalesOrder#itemSet' that was not configured
to cascade persist operations for entity:
Ziletech\Database\Entity\SalesOrderDetail#0000000052218071000000007058b4a6.
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
'Ziletech\Database\Entity\SalesOrderDetail#__toString()' to get a
clue.
You have a mistake in your mapping, cascade needs an array
/**
* #OneToMany(targetEntity="SalesOrderDetail", cascade={"all"}, mappedBy="salesOrder" )
*/
protected $itemSet;

Doctrine2 - #OneToOne unidirectional entity returns cascade persistence error

I have the following class:
/**
* #ORM\Entity(repositoryClass="Repository")
* #ORM\Table(name="my_data")
* #ORM\HasLifecycleCallbacks
*/
class Data extends ModelEntity
{
/**
* #var integer $id
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var Customer $userId
*
* #ORM\OneToOne(targetEntity="\Shopware\Models\Customer\Customer")
* #ORM\JoinColumn(name="userId", referencedColumnName="id")
*/
private $userId;
//in the class are more methods + getters and setters #not important
}
The Customer Entity is not a class of mine, therefore I want this relationship to be unidirectional and don't care how the Customer looks like (minus the id field).
Now from Doctrine documentation I got that I don't need any other cascade={"persist"} thingy like in the error I get:
exception 'Doctrine\ORM\ORMInvalidArgumentException' with message
'A new entity was found through the relationship
'Data#userId' that was not configured to cascade persist operations
for entity:
Shopware\Models\Customer\Customer#0000000035d66eab000000001b1ed901.
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
'Shopware\Models\Customer\Customer#__toString()' to get a clue.'
what am I doing wrong? And how can I fix this error?
PS: I don't want to modify or save any information in this Customer, just to have a reference to the entity.
LE: Due this info Doctrine documentation: cascade-persist I will output also some code from where the error is triggered. As it says in the documentation:
New entities in a collection not marked as cascade: persist will produce an Exception and rollback the flush() operation.
the error is trown by the following flush()
$this->entityModel->persist($this->getDataInstance());
$this->entityModel->flush();
and the getDataInstance method is Data entity that has a Customer, the Customer Object is found from its repository through method findBy('Customer', id). The Customer entity it is never new instatiated or modified, therefore no need to persist it.
The getDataInstance:
protected function getDataInstance()
{
if (is_null($this->data)) {
$this->initData();
}
return $this->data;
}
and the initData:
private function initData()
{
if (is_null($this->data)) {
$this->data = new Data();
/**
* #var \Shopware\Models\Customer\Customer $customer
*/
$customer = $this->entityModel->getRepository('Shopware\Models\Customer\Customer')->findOneBy(['id' => $this->session->sUserId]);
$this->data->setUserId($customer);
}
}
UPDATE: Still didn't find an answer, but I found some leads after speaking with someone on the doctrine #IRC channel.
Here is the conversation:
p1: did your Customer entity maybe get detached? for instance de/serialization, using more than one entity manager or calling $em->clear() can lead to detaching
p1: do you expect to create new customer? if no then either you did, or it "found" an already existing entity which would suggest one of the above
me: $em->clear() is not called by me for sure
me: the de/serialization I'm not sure ... the shop has a HTTP-cache that can do that
p1: where do you get the entity from? yes, cache might do it if it caches customers without knowing about orm
me: and I don't expect to create a new customer, I just called from the repository one
me: $customer = $this->swagModelManager->getRepository('Shopware\Models\Customer\Customer')->findOneBy(['id' => $this->session->sUserId]);
me: which retruns either null or the object
p1: is $this, respectively $this->data cached between requests possibly?
me: the method findOneBy can be find in the EntityRepository.php (line 192)
me: hmm I don't know, the thing is on my local host and other installation that never happens, but on 1 client server it does :D and I assumed that has to do with the caches and his settings on the server
me: so if that's the problem how can I fix it ?
me: to don't call the cascade persist
p1: the detached entity can be reattached by doing "$entity = $em->refresh($entity)" or "$entity = $em->merge($entity)" (they differ in handling of possible changes between the entity state and the db - refresh reloads the data from db - "resets", merge updates the db) - but that is not a fix if you do not know why it happens - you probably can't tell when it is safe to do it
me: I know, it is bad that I cannot reproduce that - thanks for the tips!
p1: the main thing - if EM says it found "new" entity and it already exists in the DB, it is not "managed" by that EM - either it has been detached somehow or it is still attached but to a different EM instance
me: then an $em->refresh($entity) before the persist should sufice, right? because I don't want to change anything related to this Entity
p1: you would have to do that to the referenced customer, so "$entity->customer = $em->refresh($entity->customer)" - but beware of possible side effects of reassigning that (any events, side effects in getter or similar stuff if you have it)
Due to the fact that I cannot reporduce the error right now, I cannot say if the end of the conversation is the right answer or not. When I will know for sure I will revise the question/answer.

Best way to implement a log with doctrine and sonata-admin

I have a case where I would like to log every change which happens on entities. I'm using sonata-admin with the doctrine admin bundle. I tried many things but I'm out of ideas what the best approach for this case would be.
The first try was creating a ChangeLog entity with the fields type (create / update), changes (array) and related entity class and id.
I setup a listener for the postUpdate und postPersist event:
appbundle.listen.ChangeLog:
class: AppBundle\Listener\ChangeLogListener
arguments: [#service_container]
tags:
- { name: doctrine.event_listener, event: postUpdate}
- { name: doctrine.event_listener, event: postPersist}
The related listener:
public function prePersist(LifecycleEventArgs $args)
{
$this->buildLog($args, ChangeLog::TYPE_CREATE);
}
public function preUpdate(LifecycleEventArgs $args){
$this->buildLog($args, ChangeLog::TYPE_UPDATE);
}
private function buildLog(LifecycleEventArgs $args, $type)
{
$entity = $args->getEntity();
$clHelper = ChangeLogHelper::getInstance();
if ($entity instanceof ChangeLog) return;
$em = $args->getEntityManager();
$changes = $em->getUnitOfWork()->getEntityChangeSet($entity);
$user = $this->container->get('security.context')->getToken()->getUser();
$cl = new ChangeLog();
$cl->setUser($user);
$cl->setDate(new \DateTime());
$cl->setChangeset($changes);
$cl->setType($type);
$cl->setEntityName(get_class($entity));
$cl->setDescription('');
$cl->setEntityId($entity->getId());
$cl->setRefGroup($clHelper->getRefId());
$em->persist($cl);
$em->flush();
}
That works for some entities but as soon as I have more relations i get the error:
Catchable Fatal Error: Argument 3 passed to Doctrine\ORM\Event\PreUpdateEventArgs::__construct() ....
I found no way to solve this problem but I have the feeling that it is caused because the listener will be called many times (for the entity itself and each relation) and it would be better to flush at the end of all listeners instead of every time it is called, but I don't see any way to do that with the post event listener setup.
After hours of debugging I thought that it probably would be better anyway if I would have a polymorphic relation on the ChangeLog entity to every other entity and I could just use the prePersist / preUpdate listeners so I don't have to persist the object myself and just set it as relation on the changed object with a proper cascade. And I'm trying to avoid the use of the entityManager in doctrine events anyway. Well hours later I'm still stuck, I couldn't find a way with doctrine to have this kind of relation. Basically One-To-Many with an extra column where the target entity is defined.
I tried to get it to work with doctrine inheritance (STI and CTI) but then my log fields are on the entity itself and not separated anymore which I don't want. I tried to solve this without the owning side on the ChangeLog entity, but then I can't set the ChangeLog entity on the changed entity because it will be ignored. But I don't know how to define the owning side with a reference to basically every other entity and I don't think it is possible with doctrine right now.
With a mapped super class and just a OneToMany relation I will get a join table or join column for every entity which is kind of messy as well.

Extending Doctrine Entity in order to add business logic

I'm trying to practice a good design and extending Doctrine entity.
My extended class, the model basically, will have extra business logic + access to the entity basic data.
I am using Doctrine 2.2.1 & Zend Framework 1.11.4 & php 5.3.8
When I use DQL, doctrine return successfully the Model entity.
When I use Doctrine native find() function, it returns nothing :(.
HELP...
This is how it rolls:
Bootstrap.php:
$classLoader = new \Doctrine\Common\ClassLoader('Entities', APPLICATION_PATH.'/doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Models', APPLICATION_PATH);
$classLoader->register();
Model in APPLICATION_PATH\models\User.php:
namespace Models;
use Doctrine\ORM\Query;
/**
* Models\User
*
* #Table(name="user")
* #Entity
*/
class User extends \Entities\User {
public function __wakeup() {
$this->tools = new Application_App_Tools();
}
Entity retrieval functions:
DOESN'T WORK:
$userEntity = $registry->entityManager->find('Models\User', $userEntity);
WORKS:
$qry = $qb
->select('u')
->from('Models\User','u');
You shouldn't add the business logic to the entities, you should use models for that instead. One way of doing it would be:
Use models for business logic.
Create custom Doctrine 2 repositories for your all your database queries (DQL or otherwise) [1].
Leave your entities alone.
In practise this means that models are plain PHP classes (or maybe framework extended depending on what your're using) but your models have no relation to your database. Your models do however, instantiate your custom Doctrine 2 repositories. E.g. a UserRepository might contain a method called getUserById. Within your repositories is where your run your actual queries and return entity instances for the models to work with.
[1] http://docs.doctrine-project.org/en/latest/reference/working-with-objects.html#custom-repositories
As I understand Doctrine, entityManager is responsible only for persistent entities, and extending Entities\User entity with Model\User will create another entity (stored in same table as stated in docblock), but not managed by entityManager or in collision with it because you probably didn't mention #InheritanceType("SINGLE_TABLE") in Entities\User docblocks:
Read this docs for more info http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
What I tried to do was a bad practice.
I coupled my DB entity and tools from zend as #Ivan HuĆĄnjak mentioned.
What should be done is de-coupling.
Business logic should be in services\controller and these should address the entity and it's methods.
you can\should add helper functions to the doctrine entity that only relates to the entity properties.
Regarding my main purpose (to have an entity class that the Doctrine CLI can rewrite & update):
doctrine only searches from changes in the native fields\methods, updates them accordingly and discard all other functions (helpers). so there is no problem when letting doctrine update the php entity!
p.s.
move to symfony2.

Categories