The user can choose which e-mail notifications he wants to receive.
Notification types are defined by enum (MyCLabs library):
use MyCLabs\Enum\Enum;
final class NotificationType extends Enum
{
public const DEADLOCKED = 1;
public const REJECTED = 2;
public const SENT = 3;
public const ACCEPTED = 4;
public const REFUSED = 5;
public function translationPath(): string
{
return 'user.notifications.'.$this->getKey();
}
}
User has more notification types:
/**
* @ORM\Entity()
*/
class Notification
{
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="notifications")
*/
protected User $user;
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
protected int $notificationType;
public function __construct(User $user)
{
$this->user = $user;
}
public function getNotificationType(): NotificationType
{
return new NotificationType($this->notificationType);
}
public function setNotificationType(NotificationType $notificationType): self
{
$this->notificationType = $notificationType->getValue();
return $this;
}
}
User entity:
/**
* @ORM\Entity()
*/
class User implements UserInterface, EquatableInterface
{
/**
* @var Collection|Notification[]
* @ORM\OneToMany(targetEntity=Notification::class, mappedBy="user", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected Collection $notifications;
//...
}
Is this the right solution? Now I have a problem to make Symfony Form for list of checkboxes.
Something like this (I know, it's wrong):
$builder->add('notifications', ChoiceType::class, [
'choices' => NotificationType::values(),
'expanded' => true,
'multiple' => true,
'choice_value' => 'value',
'choice_label' => static function (NotificationType $type): string {
return $type->translationPath();
},
]);
Can I use build-in Symfony Form in my case? Or do you have better solution for relation "Entity manyToMany Enum".
CodePudding user response:
The problem is the 'notifications' form field is mapped with entity notifications field which is a collection of notifications, and you can't map a choicetype (which expects a single notification) with a collection of notifications
you need then use a CollectionType, which will make a field for every notification (in you case the field is the ChoiceType)
read more about the CollectionType
$builder->add('notifications', CollectionType::class, [
'entry_type' => ChoiceType::class,
'entry_options' => [
'choices' => NotificationType::values(),
'expanded' => true,
'multiple' => true,
'choice_value' => 'value',
'choice_label' => static function (NotificationType $type): string {
return 'notifications.types.'.$type->getKey();
},
],
]);
CodePudding user response:
I wrote my EnumChoiceType with data transformer which transform Array of NotificationType
to Collection of Notification
entites and reverse versa.
Don't forgot set orphanRemoval=true
on collection in entity.
Usage:
$builder->add('notifications', EnumChoiceType::class, [
'enum' => NotificationType::class,
'class' => Notification::class,
'property' => 'notificationType',
'object' => $options['data'],
'expanded' => true,
'multiple' => true,
]);
EnumChoiceType:
class EnumChoiceType extends AbstractType implements DataTransformerInterface
{
/**
* @var mixed
*/
private $object;
private string $property;
private string $class;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$this->object = $options['object'];
$this->property = $options['property'];
$this->class = $options['class'];
if ($options['multiple']) {
$builder->addModelTransformer($this);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setRequired([
'enum',
]);
$resolver->setDefaults([
'choices' => static function (Options $options) {
return call_user_func([$options['enum'], 'values']);
},
'choice_value' => 'value',
'choice_label' => static function (Enum $enum): string {
return $enum->translationPath();
},
'property' => 'enum',
'object' => null,
'class' => null,
]);
}
/**
* @param mixed $value
* @return Enum[]
*/
public function transform($value): array
{
$array = [];
foreach ($value as $item) {
$array[] = PropertyAccess::createPropertyAccessor()->getValue($item, $this->property);
}
return $array;
}
/**
* @param Enum[] $value
*/
public function reverseTransform($value): ArrayCollection
{
$collection = new ArrayCollection();
foreach ($value as $type) {
$notification = new $this->class($this->object);
PropertyAccess::createPropertyAccessor()->setValue($notification, $this->property, $type);
$collection->add($notification);
}
return $collection;
}
public function getParent(): string
{
return ChoiceType::class;
}
}