Problem with get large collection from User entity Symfony 6 - php

I have a problem with get Collection from entity "User" (UserInterface, PasswordAuthenticatedUserInterface). In entity exists Collection (relation ManyToMany) to "Property" entity, it's large and have many collections.
When trying to get properties in Request Subscriber (on any other method at request level) i've got error "SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'sess_data' at row 1".
Thans for help.
Collection in User Entity:
#[ORM\ManyToMany(targetEntity: Property::class, inversedBy: 'users')]
private Collection $properties;
Method get properties
//use Symfony\Bundle\SecurityBundle\Security;
// Security $security;
/** #var User $user */
$user = $this->security->getUser();
if($user === null ) {
return null;
}
$properties = $user?->getProperties();
When get method run in controller it works fine. I storage sessions in database, and error is with size data to save (any collections from Property objects). Can I somehow bypass or disable this?
When I try to fetch by session id user from UserRepository and run method getProperties ends with the same error.

Related

Symfony4 - How to update a doctrine ArrayCollection ?

I have a member of my entity is an arrayCollection. With a classic form builder is working fine, I can select multiple items and persist it. But when I try to update an object in controller I get the error : "Call to a member function setFaavailability() on array".
A resume of my entity :
/**
* #ORM\ManyToOne(targetEntity="App\Entity\FaAvailability",
inversedBy="faavailability")
* #ORM\JoinColumn(nullable=true)
* #ORM\Column(type="array")
*/
public $faavailability;
/**
* #return mixed
*/
public function getFaavailability()
{
return $this->faavailability;
}
/**
* #param mixed $faavailability
*/
public function setFaavailability($faavailability)
{
$this->faavailability = $faavailability;
}
In my controler :
$varFaavailability = $animal->faperson->getFaavailability();
foreach($varFaavailability as $availability){
if($availability->getName() == $animal->typepet->getName()){
$varFaavailability->removeElement($availability);
$faPerson = $em->getRepository(FaPerson::class) >findById($animal->faperson->getId());
$faPerson->setFaavailability($varFaavailability);
$em->persist($faPerson);
$em->flush();
}
}
Any ideas ?
If I remember well, when you set a field as an ArrayCollection it means that you have a oneToMany relationship between two entities.
From your code, I can tell you that you are trying to persist the data in the wrong entity. You usually add the owning_entity_id(1-to-N) in each item(1-to-N) and persist it. In your code, you are trying to set all the references at once, which is never going to happen. Delete the setFaavailability() or redefine the entities' relationships.
You should never try to mass-add foreign key relationships in one super duper setter function. Cycle through all the items and set the reference to the "parent" entity.
The problem is in this part: $faPerson = $em->getRepository(FaPerson::class)->findById($animal->faperson->getId());
The findBy* methods will try to find multiple entities and return them in a Collection.
If you're looking for a single person, you can use findOneById instead. Or (assuming id is configured as identifier in Doctrine) you can even use the find method: $faPerson = $em->getRepository(FaPerson::class)->find($animal->faperson->getId());
some general comments:
In Doctrine you never have to work with the IDs. Use the entity
objects! You only need to findById if you get the ID from a request parameter for example.
You should reconsider the naming of your variables to make it clear if it is a collection ($availabilities) or a single one ($availability).
Always use the getter/setter methods instead of the fields (typepet vs getTypepet()).
Call flush() one at the end to update all entities in one single transaction.
I've renamned the variables below as I understood them. However I am still not sure what $animal->faperson->getFaavailabilities() returns, since at the beginning you wanto to loop through the results and later set it to a single one via setFaavailability()?
//Should be a Doctrine ArrayCollection
$varFaavailabilities = $animal->faperson->getFaavailabilities();
foreach($varFaavailability as $availability){
if($availability->getName() == $animal->getTypepet()->getName()) {
//Why do you want to remove an element from the current loop?
$varFaavailability->removeElement($availability);
//No need to use Id
$faPerson = $animal->getFaperson();
//A single one?
$faPerson->setFaavailability($availability);
//More than one? addFaavailability should exist.
$faPerson->addFaavailability($availability);
$em->persist($faPerson);
}
}
$em->flush();

Doctrine 2 - Persist Entity with Joined Table's Foreign Key Opposed to Foreign Entity

I'm trying to persist a TradeEntity. A TradeEntity has a OneToOne relationship with a CurrencyEntity.
/**
* #ORM\OneToOne(targetEntity="Repositories\Currency\CurrencyEntity")
* #ORM\JoinColumn(name="currency", referencedColumnName="id")
*
* #var CurrencyEntity
*/
protected $currency;
I have recieved a CurrencyEntity from another object which I'm trying to insert in this new TradeEntity and persist it to the database but get an exception:
Type: Doctrine\ORM\ORMInvalidArgumentException
Message: Expected value of type "Repositories\Currency\CurrencyEntity"
for association field "Repositories\Trade\TradeEntity#$currency", got "integer" instead.
Is there no other way of me persisting TradeEntity without fetching the CurrencyEntity from the database and setting it that way?
In light of my recent discovery, i felt the need to update this answer.
Reading about Doctrine's advanced configuration, i came across Reference Proxies.
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database.
This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this:
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Old answer:
What you can do is (it defeats the purpose of an ORM, but it is possible):
$conn = $entityManager->getConnection();
$conn->insert(
'table_name',
array(
'column_name' => 'column_value',
// define all the columns here
)
);
See Doctrine's Data Retrieval And Manipulation

Doctrine 2 - How to use objects retrieved from cache in relationships

I'm working in a project that use Doctrine 2 in Symfony 2 and I use MEMCACHE to store doctrine's results.
I have a problem with objects that are retrieved from MEMCACHE.
I found this post similar, but this approach not resolves my problem: Doctrine detaching, caching, and merging
This is the scenario
/**
* This is in entity ContestRegistry
* #var contest
*
* #ORM\ManyToOne(targetEntity="Contest", inversedBy="usersRegistered")
* #ORM\JoinColumn(name="contest_id", referencedColumnName="id", onDelete="CASCADE"))
*
*/
protected $contest;
and in other entity
/**
* #var usersRegistered
*
* #ORM\OneToMany(targetEntity="ContestRegistry", mappedBy="contest")
*
*/
protected $usersRegistered;
Now imagine that Contest is in cache and I want to save a ContestRegistry entry.
So I retrieve the object contest in cache as follows:
$contest = $cacheDriver->fetch($key);
$contest = $this->getEntityManager()->merge($contest);
return $contest;
And as last operation I do:
$contestRegistry = new ContestRegistry();
$contestRegistry->setContest($contest);
$this->entityManager->persist($contestRegistry);
$this->entityManager->flush();
My problem is that doctrine saves the new entity correctly, but also it makes an update on the entity Contest and it updates the column updated. The real problem is that it makes an update query for every entry, I just want to add a reference to the entity.
How I can make it possible?
Any help would be appreciated.
Why
When an entity is merged back into the EntityManager, it will be marked as dirty. This means that when a flush is performed, the entity will be updated in the database. This seems reasonable to me, because when you make an entity managed, you actually want the EntityManager to manage it ;)
In your case you only need the entity for an association with another entity, so you don't really need it to be managed. I therefor suggest a different approach.
Use a reference
So don't merge $contest back into the EntityManager, but grab a reference to it:
$contest = $cacheDriver->fetch($key);
$contestRef = $em->getReference('Contest', $contest->getId());
$contestRegistry = new ContestRegistry();
$contestRegistry->setContest($contestRef);
$em->persist($contestRegistry);
$em->flush();
That reference will be a Proxy (unless it's already managed), and won't be loaded from the db at all (not even when flushing the EntityManager).
Result Cache
In stead of using you own caching mechanisms, you could use Doctrine's result cache. It caches the query results in order to prevent a trip to the database, but (if I'm not mistaken) still hydrates those results. This prevents a lot of issues that you can get with caching entities themselves.
What you want to achieve is called partial update.
You should use something like this instead
/**
* Partially updates an entity
*
* #param Object $entity The entity to update
* #param Request $request
*/
protected function partialUpdate($entity, $request)
{
$parameters = $request->request->all();
$accessor = PropertyAccess::createPropertyAccessor();
foreach ($parameters as $key => $parameter) {
$accessor->setValue($entity, $key, $parameter);
}
}
Merge requires the whole entity to be 100% fullfilled with data.
I haven't checked the behavior with children (many to one, one to one, and so on) relations yet.
Partial update is usually used on PATCH (or PUT) on a Rest API.

Updating associated entity in Doctrine2

I have one User entity class, with an associated ParticularData enity class. Through a form, I want to update that ParticularData, so, if I try to get User from session:
$user = $this->getRequest()->getSession()->get('user');
$userRepository = $this->getDoctrine()->getRepository('eCommerceUserBundle:User');
$user->setParticularData($data); // $data is a ParticularData instance fetched from the form
$userRepository->update($user);
Nothing happens to database (although, for the system that ParticularData HAS changed). Then I try to get User directly from database:
$userRepository = $this->getDoctrine()->getRepository('eCommerceUserBundle:User');
$user = $userRepository->selectById(20);
$user->setParticularData($data);
$userRepository->update($user);
In that case, Doctrine2 treats that new ParticularData as a new instance, so it's trying to insert another Row in the ParticularData associated table (instead of updating the existing one).
My update method:
public function update($user){
$this->_em->merge($user);
$this->_em->flush();
}
So, how can I update an associated entity easily telling Doctrine2 to update, not to insert?
Just update the entity and call $em->flush().
If you're storing an entity in a session, you need to merge it to tell the ORM that the entity must be managed:
$user = $session->get('user');
$user = $em->merge($user);
The solution I found was to do that in my ParticularData:
/**
* Converts one ParticularData in THIS ParticularData, in order to tell Doctrine2 that the new ParticularData is the same as before; but with new fields.
* #param ParticularData $data
*/
public function cloneData($data){
$this->setAddress($data->getAddress());
$this->setCity($data->getCity());
$this->setCountry($data->getCountry());
$this->setName($data->getName());
$this->setNIN($data->getNIN());
$this->setPhone($data->getPhone());
$this->setPostalCode($data->getPostalCode());
$this->setProvince($data->getProvince());
$this->setSurname($data->getSurname());

Doctrine detaching, caching, and merging

I'm on Doctrine 2.3. I have the following query:
$em->createQuery('
SELECT u, c, p
FROM Entities\User u
LEFT JOIN u.company c
LEFT JOIN u.privilege p
WHERE u.id = :id
')->setParameter('id', $identity)
I then take that, get the result (which is an array, I just take the first element), and run detach $em->detach($result);.
When I go to fetch from the cache (using Doctrine's APC cache driver), I do:
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
if($cacheDriver->contains($cacheId))
{
$entity = $cacheDriver->fetch($cacheId);
$em->merge($entity);
return $entity;
}
My hope was that this would re-enable the relationship loading on the entity as there are many other relationships tied to the User object other than what's shown in that query.
I'm trying to create a new entity like such:
$newEntity = new Entities\ClientType();
$newEntity['param'] = $data;
$newEntitiy['company'] = $this->user['company'];
$em->persist($newEntity);
$em->flush();
When I do this, I get an error:
A new entity was found through the relationship 'Entities\ClientType#company' that was not configured to cascade persist operations for entity:
Entities\Company#000000005d7b49b500000000dd7ad743.
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 'Entities\Company#__toString()' to get a clue.
This works just fine when I don't use the company entity under the user entity that I got from cache. Is there any way to make this work so I don't have to refetch the company entity from the database every time I want to use it in a relationship with a new entity?
Edit:
This is what I have in my User entity dealing with these two relationships:
/**
* #ManyToOne(targetEntity="Company" , inversedBy="user", cascade={"detach", "merge"})
*/
protected $company;
/**
* #ManyToOne(targetEntity="Privilege" , inversedBy="user", cascade={"detach", "merge"})
*/
protected $privilege;
I am still getting the same error.
Second edit:
Trying a $em->contains($this->user); and $em->contains($this->user['company']); both return false. Which sounds... wrong.
When merging a User, you want the associated Company and Privilege to be merged as well, correct?
This process is called cascading:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations
In your User entity put cascade={"merge"} in the #ManyToOne annotation (or another type of association-definition you are using) for $company and $privilege.
And if you want the detach call to be cascaded too (which is recommended), put in cascade={"detach", "merge"}.
p.s.: Don't put such cascades on both sides of one association, you'll create an endless loop ;)
Edit:
This piece of code:
$entity = $cacheDriver->fetch($cacheId);
$em->merge($entity); // <-
return $entity;
should be:
$entity = $cacheDriver->fetch($cacheId);
$entity = $em->merge($entity); // <-
return $entity;
The thing with merge() is that it leaves the entity you pass as an argument untouched, and returns a new object that represents the managed version of the entity. So you want to use the return-value, not the argument you passed.

Categories