User(Serializable) must not be accessed before initialization symfony - 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.

Related

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

Add method in Entity not reached

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;
}

How to use KNP Translatable in a form type

I'm using KNP Translatable and I have the following data structure:
User (id, name, email, password...)
Role (id, name #translatable)
User Role is a many to many relation.
I have the form type defined as this:
->add('roles', 'entity', [
'class' => 'SocialCarBackendBundle:Role',
'property' => 'name',
'multiple' => true,
'expanded' => true
])
And I implemented the __call method in the role entity:
public function __call($method, $arguments)
{
try {
return $this->proxyCurrentLocaleTranslation($method, $arguments);
} catch (\Symfony\Component\Debug\Exception\ContextErrorException $e) {
return $this->proxyCurrentLocaleTranslation('get' . ucfirst($method), $arguments);
}
}
Now, in the twig template I can call the name property of the roles without problems and it renders it correctly.
But when trying to render the form I get this error:
Neither the property "name" nor one of the methods "getName()",
"name()", "isName()", "hasName()", "__get()" exist and have public
access in class "SocialCar\BackendBundle\Entity\Role".
Is there any workaround for this? Thanks a lot
symfony's propertyaccessor component has not magic calls enabled for EntityType property
you can see vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php to prove that.
so you have three ways(in order of complexity):
do getter and setters that call proxyCurrentLocaleTranslation, imho there are nothing bad using less magic things:)
use a more complex property like this
'property' => 'translations[' . $options['locale'] . '].name',
where $options['locale'] is the locale injected inside the form as an option
you can create a different EntityType class that extends your custom DoctrineType class that initializes PropertyAccessor to support magic calls
for more info about property accessor:
http://symfony.com/doc/current/components/property_access/introduction.html
and about the second way:
https://github.com/KnpLabs/DoctrineBehaviors/issues/67

symfony2 get all validation constraints on an entity (yml, xml, annotations)

Im trying to get all validation constraints on an entity and translate theses constraints to Jquery validation rules, right now im able to get annotation defined constraints (thanks to : Symfony2 get validation constraints on an entity), but im having some trouble getting xml and yml ones.
$xml_file_loader = new XmlFileLoader("path_to_my_project/vendor/friendsofsymfony/user-bundle\FOS\UserBundle\Resources\config\validation.xml");
Using a similar code means that i need to know beforehand where the xml/yml file is located, i m trying to write somehow a generic code that can do this automatically.
Isn't there a way to get all constraints at once? if not how can i know the location of xml/yml files, and also in cases of inheritance i need to check for parent constraints... Is this doable?
private function getValidations()
{
$validations=[];
$validator=$this->get("validator");
$metadata=$validator->getMetadataFor(new your_entity());
$constrainedProperties=$metadata->getConstrainedProperties();
foreach($constrainedProperties as $constrainedProperty)
{
$propertyMetadata=$metadata->getPropertyMetadata($constrainedProperty);
$constraints=$propertyMetadata[0]->constraints;
$outputConstraintsCollection=[];
foreach($constraints as $constraint)
{
$class = new \ReflectionObject($constraint);
$constraintName=$class->getShortName();
$constraintParameter=null;
switch ($constraintName)
{
case "NotBlank":
$param="notBlank";
break;
case "Type":
$param=$constraint->type;
break;
case "Length":
$param=$constraint->max;
break;
}
$outputConstraintsCollection[$constraintName]=$param;
}
$validations[$constrainedProperty]=$outputConstraintsCollection;
}
return $validations;
}
Returns:
array(13) (
[property1] => array(4) (
[NotBlank] => (string) notBlank
[NotNull] => (string) notBlank
[Type] => (string) string
[Length] => (int) 11
)
[property2] => array(4) (
[NotBlank] => (string) notBlank
[NotNull] => (string) notBlank
[Type] => (string) string
[Length] => (int) 40
)
..........
)
The returned array can be configured or used to define client side validation rules depending on the client-side validation library/code that you are using
$validator=$this->get("validator");
$metadata=$validator->getMetadataFor(new yourentity());
The object $metadata now contains all the metadata about validations that concerns your specific entity.

Resources