A new entity was found through the relationship - Doctrine - doctrine-orm

I have a cached entity Product. I retrieve the entity and update some properties including adding price into it.
Relationship is set up like this
class Product
{
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Price", mappedBy="product", fetch="EXTRA_LAZY")
*/
private $prices;
}
class Price
{
/**
* #var Product
*
* #ORM\ManyToOne(targetEntity="Product", inversedBy="prices")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
}
I am trying to save properties like this.
// Here $em is Entity Manager
$cacheDriver = $em->getConfiguration()->getResultCacheImpl();
$product = $cacheDriver->fetch($key)
$product->setUpdatedAt(new \DateTime());
$price = new Price;
$label = $em->getReference(PriceLabelEntity::class, $labelId);
$price->setLabel($label)
->setPriceLabelId($labelId)
->setProduct($productEntity)
->setProductId($product->getId());
$em->persist($price);
$product->addPrice($price);
$em->flush();
But whenever I do that I get the exception saying.
A new entity was found through the relationship 'Price#product' that was not
configured to cascade persist operations for entity:
Product#0000000043c0cdc400007fec6b41ca76. 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 'Product#__toString()' to get a clue.
Even more strange issue if I do just this.
$product->setUpdatedAt($timeStamp);
$em->flush();
It does not throw any error but no data is saved in DB. Not sure whether this issues are related or not either.
I tried to put cascade too but it gives different error. Is there any way to resolve this issue.

It is my understanding that Doctrine entity managers are fairly adept (at least, one would hope) at managing their result cache, so fiddling with it directly may be the issue.
Instead of hitting the result cache directly, try using:
$product = $entityManager->find('Product',$key);
Then, you also seem to have a typo on the last line - missing an "e" in "entityManager" - but I am sure that would throw an error, so it's probably a copy/paste issue when creating this question.
EDIT: You are also using both $product and $productEntity. If these are meant to be the same variable, you should pick one name and stick with it.

#Rushing thank you very much for your reply. You are right there are some typos because I had to do some changes in code to paste in StackOverflow but the actual code is correct. I am sorry I was bit late to reply on this topic. The solution you provided was good but even better solution was to use merge like this.
$productEntity = $em->merge($productEntity);
....rest of the logic
In this way we did not have to query the database again and entity was fresh.

Related

Shared Doctrine EntityManager service

We are using Symfony for our projects and there's something about Doctrine that I can't get on with.
Doctrine's entity manager (lets call it 'em' in the following) is a shared service, so when I inject em into multiple services, they share exactly the same instance of em. It is simpler If I introduce an example right away to explain what I want to ask: Consider the following example:
$service1 = $this->get('vendor_test.service_one'); // $service1 has a private entity manager property
$service2 = $this->get('vendor_test.service_two'); // $service2 as well has a private entity manager property
$entity1 = $service1->getEntityById(1); // getEntityById() queries for an entity with the given id and returns it. So it is in the managed list of service1's entity manager
$entity2 = $service2->getEntityById(2); // entity1 and entity2 not necessarily of the same class
$entity1
->setProperty1('aaaa')
->setProperty2($service2->updateDateTime($entity2)) // updateDateTime() let's say updates a datetime field of the passed entity (in this case entity2) and calls $this->entityManager->flush(); and returns the datetime.
->setProperty3('bbbb')
$service1->save(); // calls $this->entityManager->flush() so it should update the managed entities (in this case entity1)
So the question is: If the entityManager object of service1 and service2 are the same instance of entityManager so they are identical, they share the same internal managed list, then when calling $service2->updateDateTime($entity2) does an entityManager->flush(), does it flushes $entity1 as well? Does $entity1 with Property1 set to 'aaaa' being flushed midway and updated in the database, and being flushed in a second step when $service1->save(); is called?
Hope I managed to draw up what I mean and what I want to ask.
As I tested out and asked someone more competent, the answer is yes, since everywhere I use entity manager they all share the same managed list. To overcome the problem mentioned in the question, is to pass the entity to be flushed to the entity manager and all the others will be intact.

Doctrine2 entity default value for ManyToOne relation property

I've got a Doctrine2 Entity called "Order", which has several status properties. The allowed status' are stored in a different Entity, so there is a ManyToOne relation defined for those entities.
/**
* #ORM\Entity()
*/
class Order extends AbstractEntity {
// ...
/**
* #ORM\ManyToOne(targetEntity="Status")
* #ORM\JoinColumn(onDelete="NO ACTION", nullable=false)
*/
protected $status;
/** #ORM\Column(nullable=true) */
protected $stringProperty = "default value";
}
I need to set this status property to a default value when creating a new instance of the order object.
For a "non-relation" property I can simply set it like the $stringProperty above. But how to do it for relations?
I cannot set the value to the id of the related record, as Doctrine2 will complain.
It's fine if the configured default is a "Reference" to the status entity. The available status' are fixed and won't change (often).
How do I configure the entity to have a proper default relation configured.
Preferably not in a listener when persisting, as the status may be requested before that.
There are several approaches but I would suggest using the OrderRepository as a factory for creating new orders.
class OrderRepository
{
public function create()
{
$order = new Order();
$status = $this->_em->find('Status','default'); // or getReference
$order->setStatus($status);
return $order;
}
}
// In a controller
$orderRepository = $this->container->get('order_repository');
$order = $orderRepository->create();
By going with a repository you can initialize complex entity graphs that will be ready for persisting.
==========================================================================
Plan B would be to do this sort of thing within the order object and then use listeners to "fix things up" before persisting or updating.
class Order
{
public function __construct()
{
$this->status = new Status('Default');
}
}
The problem of course is that a default status object already exists in the database so when you flush you will get a error. So you need to hang an onFlush(http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#onflush) listener on the entity manager, check to see if the status object is being managed by the entity manager and, if not, replace it with a managed object fetched via the entity manager.
This approach lets you deal with more "pure" domain models without worrying as much about the persistence layer. On the other hand, dealing with the flush can be tricky. On the gripping hand, once you get it working then it does open up some major possibilities.
========================================================
There is also the question of what exactly the status entity does. If all it contains is some sort of status state ('entered',processed') etc. Then you might consider just having it be a string. Sort of like the ROLE_USER objects.

Doctrine2 - No Metadata Classes to process

Something is wrong with documentation or me. I do all what documentation says.
When i put in terminal :
$ php vendor/bin/doctrine orm:schema-tool:create
Output is :
No Metadata Classes to process
I read to many posts, and google and try to many examples but nothing.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html
I think you took the config example from Doctrine2: getting started:
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration([__DIR__."/src"], $isDevMode);
The trick is now that the Setup::createAnnotationMetadataConfiguration method uses a SimpleAnnotationReader by default. You can change this behaviour by changing the fifth parameter to false:
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration([__DIR__."/src"], $isDevMode, null, null, false);
This will force Doctrine to use the not-simple AnnotationReader which can handle your models now!
TL;DR: Make sure the type of metadata you created matches the "create metadata configuration" method you used.
I encountered the same problem while working through the Doctrine "Getting Started" guide. After looking through the Doctrine code a bit I figured out what was going wrong. Basically, the code in the tutorial in the "Obtaining the EntityManager" section is:
$config = Setup::createAnnotationMetadataConfiguration([__DIR__ . "/src"], $isDevMode);
A little further in the tutorial, in the "Starting with the Product" section, it shows us how to set up the metadata, with an example for each of the possible options for this. The tutorial says:
Metadata for entities are configured using a XML, YAML or Docblock Annotations. This Getting Started Guide will show the mappings for all Mapping Drivers. References in the text will be made to the XML mapping.
Because of this statement, I decided to use the XML configuration. Unfortunately, the createAnnotationMetadataConfiguration method in the tutorial code did not seem to be using the XML file I had created. (In hindsight, it seems much more obvious that this method is specifically referring to annotation metadata, not XML metadata!)
After I changed it to createXMLMetadataConfiguration instead, it worked as expected. So it looks like one possible source of this problem is that the "create metadata configuration" method you used may not match the type of metadata you created.
Try clearing the cache:
sudo -u www-data php app/console cache:clear --env=dev
Uncomment the one of the following lines in bootstrap.php:
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
Which depends if you created yaml or xml meta config files...
Hope this helps.
Had the same issue with custom Doctrine installation. My solution was to re-set metadata driver:
$config->setMetadataDriverImpl(
new Doctrine\ORM\Mapping\Driver\AnnotationDriver(
new Doctrine\Common\Annotations\CachedReader(
new Doctrine\Common\Annotations\AnnotationReader(),
new Doctrine\Common\Cache\ArrayCache()
),
ENTITY_DIR
)
);
Solution from http://support.skipper18.com/1120/how-use-external-tools-generate-doctrine-getters-and-setters?show=1121#a1121
My scenario was generating entities from existing database
The newDefaultAnnotationDriver adds the namespace and the method comments state the following:
If $useSimpleAnnotationReader is true, the notation #Entity will
work, otherwise, the notation #ORM\Entity will be supported.
I had the same problem when creating a new doctrine config in a new ZF2 module.
problem was caused by
'User\Entity' => 'property_entities'
the user part was from the old entity
'Property\Entity' => 'property_entities'
Changing that fixed the issue
If you're using the XML mapping (using Setup::createXMLMetadataConfiguration()), you might want to pay attention to the following:
That your XML mapping files ends by .dcm.xml, not only by .xml.
That your XML file contains the full entity classname, inclusive of the namespace. For example, for a class Company\Solution\Models\User, you must have the Company.Solution.Models.User.dcm.xml mapping file in your XML path.
In my case, the issue was with the number of asterisk used for the annotation
<?php
namespace Models;
use Doctrine\ORM\Annotation\{Id, Column, GeneratedValue, Entity};
/** // I originally used one asterisk here and kept getting the error in question. Error disappeared after doubling the asterisk as it is in this answer
* #Entity(repositoryClass="Doctrine\ORM\Annotation\Id")
*/
class User {
}
?>
you must add the docstring for example:
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
}
Try the following command:
php vendor/bin/doctrine-module orm:schema-tool:create

How to update an object with ManyToMany relation with doctrine 2

Hi there Stack Overflow members!
I have doctrine 2 installed everything work perfectly,
I have generated my entities and proxies with command ,
my problem is when I try to update an entities with many to many relation ship
I had this problem
Fatal error: Uncaught exception 'PDOException' with message
'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'8-1' for key 'PRIMARY''
it seems that doctrine try to insert a new entities and not trying to update joined table
if there any problem in my code , is there any example clear ?
thanks
//update entities user
$user=$this->em->getRepository('Entities\User')->find((int)$this->input->post('id'));
$user->setNom($this->input->post('nom'));
$user->setPrenom($this->input->post('prenom'));
//update entities services(user have many service)
foreach($this->input->post('services') as $post){
$service = $this->em->getRepository('Entities\Service')->find((int)$post);
if ($service instanceof Entities\Service) {
$user->addService($service);
}
$this->em->flush();
As far as I can see from your example, you are not persisting any entities, therefore Doctrine doesn't register any changes, and flush has nothing to update.
You have to persist the owning side of the relationship.
$this->em->persist($service);
or
$this->em->persist($user);
Depends what did you specify as the owning side.
Check out the documentation

How to manually set a primary key in Doctrine2

I am importing data into a new Symfony2 project using Doctrine2 ORM.
All new records should have an auto-generated primary key. However, for my import, I would like to preserve the existing primary keys.
I am using this as my Entity configuration:
type: entity
id:
id:
type: integer
generator: { strategy: AUTO }
I have also created a setter for the id field in my entity class.
However, when I persist and flush this entity to the database, the key I manually set is not preserved.
What is the best workaround or solution for this?
The following answer is not mine but OP's, which was posted in the question. I've moved it into this community wiki answer.
I stored a reference to the Connection object and used that to manually insert rows and update relations. This avoids the persister and identity generators altogether. It is also possible to use the Connection to wrap all of this work in a transaction.
Once you have executed the insert statements, you may then update the relations.
This is a good solution because it avoids any potential problems you may experience when swapping out your configuration on a live server.
In your init function:
// Get the Connection
$this->connection = $this->getContainer()->get('doctrine')->getEntityManager()->getConnection();
In your main body:
// Loop over my array of old data adding records
$this->connection->beginTransaction();
foreach(array_slice($records, 1) as $record)
{
$this->addRecord($records[0], $record);
}
try
{
$this->connection->commit();
}
catch(Exception $e)
{
$output->writeln($e->getMessage());
$this->connection->rollBack();
exit(1);
}
Create this function:
// Add a record to the database using Connection
protected function addRecord($columns, $oldRecord)
{
// Insert data into Record table
$record = array();
foreach($columns as $key => $column)
{
$record[$column] = $oldRecord[$key];
}
$record['id'] = $record['rkey'];
// Insert the data
$this->connection->insert('Record', $record);
}
You've likely already considered this, but my approach would be to set the generator strategy to 'none' for the import so you can manually import the existing id's in your client code. Then once the import is complete, change the generator strategy back to 'auto' to let the RDBMS take over from there. A conditional can determine whether the id setter is invoked. Good luck - let us know what you end up deciding to use.