Add method in Entity not reached - symfony

I need to bugfix a Symfony3 project with Sonata Admin & Doctrine.
I got a field list, with all textures saved in base
The problem is in the "addTexture" method, the previous developer did :
public function addTexture(\AppBundle\Entity\Texture $texture)
{
if (count($this->textures[]) < 26)
$this->textures[] = $texture;
return $this;
}
And we can add more than 25 textures : that's my bug.
So I added a dump($this->textures); exit; and nothing happen, the app never reach this. While in other entities, the "add" method is reached.
EDIT :
Seems like it's my Doctrine2 ArrayCollection that never fire all my addXXX methods. But it saving my collections on database, and i don't know why.
EDIT 2 :
I found where the list is declared :
->add('colors','sonata_type_model', [
'multiple' => true,
'expanded' => false,
'property' => 'Name'
])
Is there a way to add a limit or maximum item ? Then I won't need to update the Entity if it's UI restricted.

Try to use ArrayCollection methods (let's say that we work on Color Entity):
public function addTexture(\AppBundle\Entity\Texture $texture)
{
if ($this->textures->count() < 26) {
$this->textures->add($texture);
$texture->setColor($this); //Add this if you have bidirectional mapping
}
return $this;
}

Related

User(Serializable) must not be accessed before initialization symfony

When i try to connect as a user (my user entity implement UserInterface), i always get this error:
Typed property Symfony\Component\Security\Core\Exception\AccountStatusException::$user must not be accessed before initialization
At: D:\cours\symfony\blog\vendor\symfony\security-core\Exception\AccountStatusException.php:45
So i implement *Serializable* like someone says it here: https://github.com/symfony/symfony/issues/38274#issuecomment-697231222 like this:
public function serialize(): array {
return ['id' => $this->getId(), 'email' => $this->getEmail(), 'password' => $this->getPassword(), 'roles' => $this->getRoles()];//FIXME ajouter rôle?
}
public function unserialize($serialized): void {
list($this->id, $this->name, $this->email, $this->roles) = unserialize($serialized);
}
But i still get "User must not be accessed before initialization". 🤔 So maybe implementing \Serializable is the old way to do it (from 2020).
I just need to update all my bundle. Like my last edit suggested: Somes of my bundles was too old.

Symfony 5 / Easy Admin 3 - FormBuilder added field not displaying appropiate input

I am building a form using Easy Admin's FormBuilder. My goal is to have an AssociationField which represents a OneToMany relationship, for example, to assign multiple products to a shop. Additionally, I only want some filtered products to be listed, so I overrode the createEditFormBuilder method in the CrudController, I used this question as reference, and this is the code for the overridden function :
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$filteredProducts = $context->getEntity()->getInstance()->getFilteredProducts();
$formBuilder->add('products', EntityType::class, ['class' => 'App\Entity\Product', 'choices' => $filteredProducts, 'multiple' => true]);
return $formBuilder;
}
I expected an Association field as the ones configured in the configureFields() function, however, the displayed field doesn't allow text search or autocomplete features, plus has incorrect height.
Expected:
Actual:
I tried to change the second argument in the $formBuilder->Add() function, but all specific EasyAdmin types threw errors.
UPDATE: I also tried using EasyAdmin's CrudFormType instead of EntityType, which doesn't support the 'choice' parameter. Still, the result was the same.
There is setQueryBuilder on the field, you can use it for filtering entities like this
<?php
// ...
public function configureFields(string $pageName): iterable
{
// ...
yield new AssociationField::new('products')->setQueryBuilder(function($queryBuilder) {
$queryBuilder
->andWhere('entity.id IN (1,2,3)')
;
})
;
// ...
}

EntityType and many to many with extra field relation presented as dropdown (select)

I created a form like that
$builder->add('employees', EntityType::class, [
'class' => ActivityEmployee::class,
'choice_label' => function (ActivityEmployee $employee) {
return sprintf('%s %s', $employee->getEmployee()->getName(), $employee->getEmployee()->getLastName());
},
'multiple' => true,
])
As a result it presents already existing data fine. It shows me all employees with relation to edited activity.
However as choices there should be all employess to choose (employee entity) and as selected data only employess in activityEmployee relation like right now.
I tried to add a query_builder option to provide lists of all employess, but I can only use EntityRepository which means ActivityEmployeesRepository not EmployeesRepository per se.
A can't figure out how to implement it. Basically such relation can be done by CollectionType of custom activityEmployeeType but I'd like to use multi-select for selecting employees.
I can use another approach to not mapping my employees field to entity like that
$currentEmployees = [];
foreach ($activity->getEmployees() as $activityEmployee) {
$currentEmployees[] = $activityEmployee->getEmployee();
}
$builder->add('employees', EntityType::class, [
'class' => Employee::class,
'choice_label' => function (Employee $employee) {
return sprintf('%s %s', $employee->getName(), $employee->getLastName());
},
'mapped' => false,
'multiple' => true,
'data' => $currentEmployees,
]);
It works fine, but I need to deal with updating relation by myself. Which is ok, however I wonder how to achieve such thing in first approach.
Implementation details matter. As far as I can understand you have the following entities:
Activity (entity)
- employees (OneToMany -> ActivityEmployee)
ActivityEmployee (entity)
- activity (ManyToOne -> Activity)
- employee (ManyToOne -> Employee)
Employee (entity)
- activities (OneToMany -> ActivityEmployee) - this one might be missing, actually.
Now you apparently don't hide any implementation details. Meaning, your Activity::getEmployees() returns []ActivityEmployee.
I would have done it like this:
class Activity {
/** #ORM\OneToMany(targetEntity=ActivityEmployee::class) */
private $activityEmployees;
/** #return Employee[] */
public function getEmployees() :Collection {
return $this->activityEmployees->map(function(ActivityEmployee $ae) {
return $ae->getEmployee();
});
}
public function addEmployee(Employee $employee) {
// check, if the employee is already registered, add only then!
if(!$this->getEmployees()->contains($employee)) {
$this->activityEmployees->add(new ActivityEmployee($this, $employee));
}
}
public function removeEmployee(Employee $employee) {
foreach($this->activityEmployees as $activityEmployee) {
if($activityEmployee->getEmployee() === $employee) {
$this->activityEmployees->removeElement($activityEmployee);
}
}
}
}
This way, you hide away how Activity handles the employees and to the outside world (and specifically the PropertyAccessor, that the form component uses) it appears as if Activity has a property employees which are actually Employee[].
If you implement it like this, your first form should actually just work (obviously exchanging ActivityEmployee for Employee) - under the assumption that I didn't make some major mistake. Of course I would also add methods like getActivityEmployees when I would actually specificially need the relation objects.
This whole thing certainly is less beautiful if your many-to-many can contain duplicates.
IF your ActivityEmployee actually has NO other properties besides activity and employee, you could obviously replace the whole thing with a #ORM\ManyToMany and just work with Employee[] instead of the ActivityEmployee[]. However, I assume you have some additional columns like created or something.

Doctrine weird behavior, changes entity that I never persisted

I have this situation:
Symfony 4.4.8, in the controller, for some users, I change some properties of an entity before displaying it:
public function viewAction(string $id)
{
$em = $this->getDoctrine()->getManager();
/** #var $offer Offer */
$offer = $em->getRepository(Offer::class)->find($id);
// For this user the payout is different, set the new payout
// (For displaying purposes only, not intended to be stored in the db)
$offer->setPayout($newPayout);
return $this->render('offers/view.html.twig', ['offer' => $offer]);
}
Then, I have a onKernelTerminate listener that updates the user language if they changed it:
public function onKernelTerminate(TerminateEvent $event)
{
$request = $event->getRequest();
if ($request->isXmlHttpRequest()) {
// Don't do this for ajax requests
return;
}
if (is_object($this->user)) {
// Check if language has changed. If so, persist the change for the next login
if ($this->user->getLang() && ($this->user->getLang() != $request->getLocale())) {
$this->user->setLang($request->getLocale());
$this->em->persist($this->user);
$this->em->flush();
}
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::TERMINATE => [['onKernelTerminate', 15]],
];
}
Now, there is something very weird happening here, if the user changes language, the offer is flushed to the db with the new payout, even if I never persisted it!
Any idea how to fix or debug this?
PS: this is happening even if I remove $this->em->persist($this->user);, I was thinking maybe it's because of some relationship between the user and the offer... but it's not the case.
I'm sure the offer is persisted because I've added a dd('beforeUpdate'); in the Offer::beforeUpdate() method and it gets printed at the bottom of the page.
alright, so by design, when you call flush on the entity manager, doctrine will commit all the changes done to managed entities to the database.
Changing values "just for display" on an entity that represents a record in database ("managed entity") is really really bad design in that case. It begs the question what the value on your entity actually means, too.
Depending on your use case, I see a few options:
create a display object/array/"dto" just for your rendering:
$display = [
'payout' => $offer->getPayout(),
// ...
];
$display['payout'] = $newPayout;
return $this->render('offers/view.html.twig', ['offer' => $display]);
or create a new non-persisted entity
use override-style rendering logic
return $this->render('offers/view.html.twig', [
'offer' => $offer,
'override' => ['payout' => $newPayout],
]);
in your template, select the override when it exists
{{ override.payout ?? offer.payout }}
add a virtual field (meaning it's not stored in a column!) to your entity, maybe call it "displayPayout" and use the content of that if it exists

foreign key not persisted symfony2 after a clone

I'm using symfony + Doctrine and I'm stuck with a problem:
I cloned an existing object and I would like to change a FK on the clone. It should be like that:
$dafCloned = clone $daf;
$dafState = $dafStateRepository->findOneBy(
array(
'name' => 'saved',
'dafType' => 'invoice',
'company' => $daf->getSeller(),
));
$dafCloned->setDafState($dafState);
var_dump($dafState->getId());
var_dump($dafCloned->getDafState()->getId());
$this->em->persist($dafCloned);
$this->em->flush();
As you may have noticed, I got 2 var_dump here. Here are the print of the Custom Command calling this code :
int(5500)
int(5499)
5500 is the id I should have in db for $dafCloned, 5499 is the id I have for $daf.
I'd like to know WHY I got different id...My dafState should be the same. I'm probably missing something really stupid but I'm stuck on it since 9am...I even tried to delete all caches we have, moving flush() and persist() but cant help :s
EDIT : added the setDafState() method if needed, but this is basic :
public function setDafState(DafState $dafState) {
$this->dafState = $dafState;
return $this;
}
EDIT2 :
Here getDafState() :
/**
* Get dafState
*
* #return MyPath\Entity\DafState
*/
public function getDafState() {
return $this->dafState;
}
If you need more code sample, just ask for it, I'll edit ;)
For the object, both are huge (Doctrine Object) and i can't find any way to get what could be useful :s. I cant grep dafState on $daf Object, output is still huge.
EDIT 3 :
if ($daf->getId() == 8902) // daf test which should be duplicated
var_dump($dafCloned->getDafState() === $dafState);
output
bool(true)
$dafCloned = clone $daf; // Here your clone is the same object as the old one
$dafState = $dafStateRepository->findOneBy( // Here you get some fresh object
array(
'name' => 'saved',
'dafType' => 'invoice',
'company' => $daf->getSeller(),
));
$dafCloned->setDafState($dafState); // Because this object is still managed by the entity manager it will set the $dafState on the old object (tracked by Id most likely)
var_dump($dafState->getId()); // Show the Id on the fresh object
var_dump($dafCloned->getDafState()->getId()); // Show the Id on the old object
$this->em->persist($dafCloned); // overwrite the old object
$this->em->flush();
This Post will be helpful to you: How to re-save the entity as another row in Doctrine 2
I will update my answer if this doesn't solve your issue
Here we go.
Thanks to #cheesemacfly i find out i have a prePersistListener which was resetting my dafState !
So, next time you have something weird looking like the above problem, check your listener !

Resources