Home > Software design >  Pre set collection in symfony form and save only not empties
Pre set collection in symfony form and save only not empties

Time:10-26

I have Week and WeekDays. I need to edit a Week with all possible WeekDays (monday at lunch, monday at dinner, tuesday at lunch, tuesday at dinner, etc...) even if they are not persisted.

Week :

<?php

namespace App\Entity;

use App\Repository\WeekRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=WeekRepository::class)
 */
class Week
{
    /**
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     */
    private Uuid $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="Merci de donner un nom à votre semaine type.")
     * @Assert\Length(max=255, maxMessage="Le nom de votre semaine type ne peut pas faire plus de 255 caractères.")
     */
    private ?string $name;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="weeks")
     * @ORM\JoinColumn(nullable=false)
     */
    private ?User $user;

    /**
     * @ORM\OneToMany(targetEntity=WeekDay::class, mappedBy="week", orphanRemoval=true, cascade={"persist"})
     */
    private Collection $days;

    public function __construct()
    {
        $this->id = Uuid::v6();
        $this->days = new ArrayCollection();
    }

    public function getId(): Uuid
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    public function setUser(?User $user): self
    {
        $this->user = $user;

        return $this;
    }

    public function hasDay(WeekDay $checkday): bool
    {
        foreach ($this->getDays() as $day) {
            if (
                $day->getDay() === $checkday->getDay() &&
                $day->getTime() === $checkday->getTime()
            ) {
                return true;
            }
        }

        return false;
    }

    public function getDays(): Collection
    {
        return $this->days;
    }

    public function addDay(WeekDay $day): void
    {
        if (!$this->hasDay($day)) {
            $this->days[$day->getDay().$day->getTime()] = $day;
            $day->setWeek($this);
        }
    }

    public function removeDay(WeekDay $day): void
    {
        $this->days->removeElement($day);
        $day->setWeek(null);
    }
}

WeekDay :

<?php

namespace App\Entity;

use App\Repository\WeekDayRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;

/**
 * @ORM\Entity(repositoryClass=WeekDayRepository::class)
 */
class WeekDay
{
    private const MONDAY = 1;
    private const TUESDAY = 2;
    private const WEDNESDAY = 3;
    private const THURSDAY = 4;
    private const FRIDAY = 5;
    private const SATURDAY = 6;
    private const SUNDAY = 7;

    private const TIME_LUNCH = 1;
    private const TIME_DINNER = 2;

    /**
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     */
    private Uuid $id;

    /**
     * @ORM\Column(type="integer")
     */
    private int $day;

    /**
     * @ORM\Column(type="integer")
     */
    private int $time;

    /**
     * @ORM\Column(type="boolean")
     */
    private bool $used = false;

    /**
     * @ORM\ManyToOne(targetEntity=Week::class, inversedBy="days")
     * @ORM\JoinColumn(nullable=false)
     */
    private ?Week $week;

    /**
     * @ORM\ManyToMany(targetEntity=Theme::class)
     */
    private Collection $themes;

    /**
     * @ORM\Column(type="array", nullable=true)
     */
    private array $preparations = [];

    public function __construct()
    {
        $this->id = Uuid::v6();
        $this->themes = new ArrayCollection();
    }

    public function getId(): Uuid
    {
        return $this->id;
    }

    public function getDay(): ?int
    {
        return $this->day;
    }

    public function setDay(int $day): self
    {
        $this->day = $day;

        return $this;
    }

    public static function getDays(): array
    {
        return [
            self::MONDAY => 'monday',
            self::TUESDAY => 'tuesday',
            self::WEDNESDAY => 'wednesday',
            self::THURSDAY => 'thursday',
            self::FRIDAY => 'friday',
            self::SATURDAY => 'saturday',
            self::SUNDAY => 'sunday',
        ];
    }

    public function getDayLabel()
    {
        $labels = self::getDays();

        return $labels[$this->getDay()] ?? '-';
    }

    public function getTime(): ?int
    {
        return $this->time;
    }

    public function setTime(int $time): self
    {
        $this->time = $time;

        return $this;
    }

    public static function getTimes(): array
    {
        return [
            self::TIME_LUNCH => 'lunch',
            self::TIME_DINNER => 'dinner',
        ];
    }

    public function getTimeLabel()
    {
        $labels = self::getTimes();

        return $labels[$this->getTime()] ?? '-';
    }

    public function isUsed(): bool
    {
        return $this->used;
    }

    public function setUsed(bool $used): self
    {
        $this->used = $used;

        return $this;
    }

    public function getWeek(): ?Week
    {
        return $this->week;
    }

    public function setWeek(?Week $week): self
    {
        $this->week = $week;

        return $this;
    }
}

My actual form :

class WeekType extends AbstractType
{
    private WeekService $service;

    public function __construct(WeekService $service)
    {
        $this->service = $service;
    }

    /** {@inheritdoc} **/
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('days', CollectionType::class, [
                'entry_type' => WeekDayType::class,
                'by_reference' => false,
                'allow_add' => true,
                'allow_delete' => true,
                'data' => $this->service->getAllSortedDays($builder->getData()),
            ])
        ;

        $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
            $form = $event->getForm();
            $data = $event->getForm()->getData();

            $form
                ->add('days', CollectionType::class, [
                    'entry_type' => WeekDayType::class,
                    'by_reference' => false,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'data' => $this->service->getAllSortedDays($data),
                ])
            ;
        });
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Week::class,
        ]);
    }
}

Method to get all sorted days :

    public function getAllSortedDays(Week $week): Collection
    {
        $weekDays = WeekDay::getDays();
        $times = WeekDay::getTimes();

        foreach (array_keys($weekDays) as $dayId) {
            foreach (array_keys($times) as $timeId) {
                $weekDay = (new WeekDay())
                    ->setDay($dayId)
                    ->setTime($timeId)
                ;

                $week->addDay($weekDay);
            }
        }

        $orderBy = (Criteria::create())->orderBy([
            'day' => Criteria::ASC,
            'time' => Criteria::ASC,
        ]);

        return $week->getDays()->matching($orderBy);
    }

For now, the form on page load is OK, I get all WeekDays.

I subscribed to SUBMIT event to show all WeekDays if I have error on submit : if I don't do that, I only have submitted WeekDays.

The problem now is if I submit like this, it save all WeekDays, even empty ones, and I would prefer to avoid it.

CodePudding user response:

I have removed allow_delete option, so I can get ride of SUBMIT event, defaults empties objects are not automatically deleted anymore.

So I get them if there are errors in form, but I have to remove them manually in a POST_SUBMIT event to avoid persistence.

    $builder
        ->add('name')
        ->add('days', CollectionType::class, [
            'entry_type' => WeekDayType::class,
            'by_reference' => false,
            'data' => $this->service->getAllSortedDays($builder->getData()),
        ])
    ;

    $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
        $data = $event->getForm()->getData();

        foreach ($data->getDays() as $day) {
            if (
                false === $day->isUsed() &&
                0 === count($day->getThemes()) &&
                0 === count($day->getPreparations())
            ) {
                $data->removeDay($day);
            }
        }
    });
  • Related