Doctrine launches INSERT always instead of UPDATE for existing entities - php

Let's say I have the following entities:
App\Entity\MainEntity:
/**
* #var object
*
* #ORM\OneToOne(targetEntity="App\Entity\DependentEntity", fetch="EAGER")
* #ORM\JoinColumn(name="DependentEntityType1FK", referencedColumnName="DependentEntityIDPK")
*/
private $dependentEntityType1;
/**
* #var object
*
* #ORM\OneToOne(targetEntity="App\Entity\DependentEntity", fetch="EAGER")
* #ORM\JoinColumn(name="DependentEntityType2FK", referencedColumnName="DependentEntityIDPK")
*/
private $dependentEntityType2;
Basically, one-directional 1:1 relationship from main entity to the same dependent entity using two different columns in the main entity table.
It doesn't matter, whether I use fetch="EAGER" or normal lazy loading through Doctrine proxy classes, when I do something like this:
$mainEntity = $this->mainEntityRepository->find(74);
$mainEntity->setDependentEntityType1($this->dependentEntityRepository->find(35));
$this->mainEntityRepository->saveTest($mainEntity);
where ::saveTest() is:
public function saveTest(MainEntity $mainEntity) {
$this->_em->persist($mainEntity->getDependentEntityType1());
$this->_em->merge($mainEntity);
$this->_em->flush();
}
it always tries to INSERT a new dependent entity to the table, even though I never made any changes (and even if I made them, it should have been UPDATE! for it)
The question is: why does Doctrine decide this dependent entity is a new one if I did $this->dependentEntityRepository->find(35) , so loaded an existing one?
I tried fetch="EAGER" thinking that spl_object_hash might return different hashes for a Proxy class instance and the actual DependantEntity one, but it doesn't matter, the DependantEntity is for some reason always considered as "new".
UPDATE: here is the code of ::setDependentEntityType1()
public function setDependentEntityType1(DependentEntity $dependentEntity) : void {
$this->dependentEntity = $dependentEntity;
}

Related

Can I use the same table to represent different Entities in Symfony?

I am migrating an old PHP project to Symfony. I am trying to create the entities based on the existing database schema which I can not change. I am facing a problem :
There is a table that would represent two different entities. Basically, there is a boolean (a tinyint(1)), if the boolean is false, then the row of the table is representing a cart. If the boolean is true, then the row is representing an order.
Is it possible for Doctrine to make the distinction between these and to fetch those entities accordingly ? The solution I was willing to implement was creating several entities and overwrite the find() and findAll() methods in these entities' repositories. Is there another way to achieve that ?
This is what doctrine call Inheritance Mapping.
So you'll have one Cart entity and one Order entity extended it.
/**
* #ORM\Table()
* #ORM\Entity(repositoryClass="App\Repository\CartRepository")
* #ORM\InheritanceType(value="SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="is_order", columnDefinition="BOOL DEFAULT FALSE")
* #ORM\DiscriminatorMap(
* value={
* CART::IS_CART=Cart::class,
* CART::IS_ORDER=Order::class
* }
* )
*/
class Cart {
const IS_CART = FALSE;
const IS_ORDER = TRUE;
... // Entity field, getters, setters, functions...
}
Then your Order Entity.
/**
* #ORM\Entity(repositoryClass=OrderRepository::class)
*/
class Order extends Cart {...}
There is maybe some mistake in this code I didn't test it but it should be ok.

Doctrine in Symfony: use a single “Author” associative entity related to different entities

I'm developing a custom content management system with Symfony 5 and Doctrine.
I'm trying to implement a relation between the entities Document and Video (actually there are many more, but for simplicity sake let's say are just two) and the User entity.
The relation represent the User who wrote the document or recorded the video. So the relation here is called Author. Each document or video can have one or more author. Each User can have none or more document or video.
I would like to use just a single associative Author associative entity, like this:
entity_id|author_id|entity
Where:
entity_id: is the id of the document or video
author_id: is the user_id who authored the entity
entity: is a constant like document or video to know to which entity the relation refer to
The problem is that I cannot understand how to build this in Doctrine. Was this a classic SingleEntity<-->Author<-->Users relationship I would have build it as a ManyToMany item, but here it's different.
Author would probably contain two ManyToOne relations (one with the User entity and one with either the Document or the Video entity) plus the entity type field, but I really don't know how to code the "DocumentorVideo`" part. I mean:
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=??????????, inversedBy="authors")
* #ORM\JoinColumn(nullable=false)
*/
private $entity; // Document or Video
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="articles")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="smallint")
*/
private $entityType;
How should I manage the first field?
Don't know if would be better to store it under two differents attributes. If not and mandatory, I think those "objects" should have a common interface or something, so take a look to doctrine inheritance that should fulfill your needs
My suggestion is to store the entity namespace Ex. Acme\Entity\Document in a property and the id in another and to use the entity manager to get the entity.
Edit: Though you won't have the relation, I prefer that way over others because it is reusable and the performance is rather the same. Also if I need to pass it to a JSON response, I just create a normalizer and I am good to go.
/**
* #ORM\Column(type="string")
*/
private $entityNamespace;
/**
* #ORM\Column(type="integer")
*/
private $entityId;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getEntity()
{
return $this->em->getRepository($this->entityNamespace)->find($this->entityId);
}

Symfony 3 / Doctrine - Get changes to associations in entity change set

So I already know that I can get changes to a specific entity in the preUpdate lifecycle event:
/**
* Captures pre-update events.
* #param PreUpdateEventArgs $args
*/
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof ParentEntity) {
$changes = $args->getEntityChangeSet();
}
}
However, is there a way to also get changes for any associated Entities? For example, say ParentEntity has a relationship setup like so:
/**
* #ORM\OneToMany(targetEntity="ChildEntity", mappedBy="parentEntity", cascade={"persist", "remove"})
*/
private $childEntities;
And ChildEntity also has:
/**
* #ORM\OneToMany(targetEntity="GrandChildEntity", mappedBy="childEntity", cascade={"persist", "remove"})
*/
private $grandChildEntities;
Is there a way to get all relevant changes during the preUpdate of ParentEntity?
All of the associated entities from a OneToMany or ManyToMany relationships appear as a Doctrine\ORM\PersistentCollection.
Take a look at the PersistentCollection's API, it have some interesting public methods even if they are marked as INTERNAL: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/PersistentCollection.php#L308
For example you can check if your collection is dirty which means that its state needs to be synchronized with the database. Then you can retrieve the entities that have been removed from the collection or inserted into it.
if ($entity->getChildEntities()->isDirty()) {
$removed = $entity->getChildEntities()->getDeleteDiff();
$inserted = $entity->getChildEntities()->getInsertDiff();
}
Also you can get a snapshot of the collection at the moment it was fetched from the database: $entity->getChildEntities()->getSnapshot();, this is used to create the diffs above.
May be this is not optimal, but it can do the job. You can add a version field on ParentEntiy with a timestamp, then on each related entity setter function (Child or GranChild) you need to add a line updating that parent timestamp entity. In this way each time you call a setter you will produce a change on the parent entity that you can capture at the listener.
I have used this solution to update ElasticSearch documents that need to be updated when a change happens on a child entity and it works fine.

Symfony : is it bad practice to add custom functions in an Entity?

I understand that an Entity is a basic class that holds data.
But is it bad practice if the Entity has custom functions that manipulates the data ?
I personally think that this kind of functions should go into a different Service. But in this case, the getNextPayroll is quite useful :
<?php
class Payroll
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="last_payroll", type="datetime", nullable = true)
*/
private $lastPayroll;
/**
* Set lastPayroll
*
* #param \DateTime $lastPayroll
* #return CompanyBase
*/
public function setLastPayroll($lastPayroll)
{
$this->lastPayroll = $lastPayroll;
return $this;
}
/**
* Get lastPayroll
*
* #return \DateTime
*/
public function getLastPayroll()
{
return $this->lastPayroll;
}
public function getNextPayroll()
{
$payrollNext = clone $this->getLastPayroll();
$payrollNext->add(new \DateInterval("P1M"));
return $payrollNext;
}
}
The date of the next payroll is not stored in database. Only the date of the last payroll. Should I get the next payroll date in a different service or is it OK to use use a custom function non-generated by doctrine in an entity ?
It's not a bad practice, if your code still satisfies SOLID principles (mainly, Single Responsibility principe in that case)
So, if the method isn't related to entity logic (for example, sending emails or persisting something to database right from your entity) -- it's wrong. Otherwise, it's absolutely OK.
The major attribute of the logic related to entity -- it should be in the same layer with another stuff in the entity.
Actually, Doctrine entities is not just Data Transfer Objects (without behavior). Doctrine's developers insist using the entities as Rich Models (look the video by Marco Pivetta, one of Doctrine's developers and see his nice presentation)
As far as I know it shouldn't be bad practice as long as your entity doesn't get into the Database layer, which the Repository should take care of.
So things where you more or less just get EntityData back (like your method which returns just modified data that belongs to the Entity) should be fine in the Entity it self. This way you also easily use the methods inside Twig, which automaticaly searchs for the method name (ex. {{ User.name }} will search for User->getName() and if it doesn't find it, it will search for User->name())
If you reusing this part and want to be dynamic, it also could be a good idea to create a custom Twig extension.
I think you will only need a service if you going to do very complicated things where you actually also need to inject the EntityManager and also retrive data from other entities that maybe are not part of the usual relations.

How do I search by properties of entity which do not have #Column anotation in Doctrine2?

/**
* #ORM\Entity
*/
class Order extends BaseEntity
{
// this is trait for #Id
use Identifier;
/**
* #ORM\Column(type="integer")
*/
protected $costPerUnit;
/**
* #ORM\Column(type="integer")
*/
protected $numberOfUnits;
// i want to search by this property
protected $totalCost;
public function getTotalCost()
{
return $this->numberOfUnits * $this->costPerUnit;
}
}
I have an entity like this and I'd like to be able to do for example
$orderRepository->findOneByTotalCost('999')
$orderRepository->findBy(['totalCost' => '400']);
Is this possible in Doctrine2? Or would I go about it differently?
Like I said in my comments, it's likely you're wrestling with an issue that shouldn't have occurred in the first place. Still, having a SUM value mapped to a property is possible using doctrine, in a variety of ways: Check the aggregate field docs to find out which would solve your problem best.
To my eyes, is that you're using entities as more than what they really are: Entities represent records in a database, Doctrine is a DBAL. Searching data using entities (or repositories) is querying the database. You could solve the problem by adding custom methods to your entity manager or a custom repository class that'll query all of the data required to compute the totalCost value for all entities, and return only those you need. Alternatively, use the connection from your DBAL to query for the id's you're after (for example), then use those values to get to the actual entities. Or, like I said before: use aggregate fields.
The problems you have with the findOneByTotalCost and findBy examples you show is that the first requires you to write a method Called findOneByTotalCost yourself. The problem with your use of findBy is simply that your argument is malformed: the array should be associative: use the mapped column names as keys, and the values are what you want to query for:
$repo->findBy(
['totalCost' => 400]
);
is what you're looking for, not ['totalCost', 400]. As for the entity itself, you'll need to add an annotation:
Yes it is, judging by your use of #ORM\Entity annotations in the doc-blocks, this ought to do it:
/**
* #ORM\Column(type="string", length=255)
*/
protected $regioun = 'Spain';
The update the table, and you'll be able to:
$entities = $repo->findBy(
['region' => 'Spain']
);
Don't forget that this code represents a table in a DB: you can search on any of the fields, but use indexes, which you can do by adding annotations at the top of your class definition:
/**
* #ORM\Table(name="tblname", indexes={
* #ORM\Index(name="region", columns={"region"})
* })
*/
class Foo
{}
As ever: in DB's, indexes matter
You should write a method findOneByTotalCost on your entity repository, something like:
public function findOneByTotalCost ($queryParams){
$query = 'select o
from <yourEntity> o
where o.numberOfUnits * o.costPerUnit = :myParam';
$dql = $this->getEntityManager()->createQuery($query);
$dql->setParameter('myParam', $queryParams);
return $dql ->execute();
}
Them, $orderRepository->findOneByTotalCost('999') should work.

Categories