具有检查的Symfony表单类型(如果已存在)(LocationType)(Tried EventListener,Transformer ....)

My wish

I want a independent form type for my Location entity which I can add everywhere I want. When it's null then it should be null and not an empty Location.

Current problem

My problem is either that when I submit null then the previous set location will be used or when I fixed this issue its that the location is duplicated and not unique.

Base code

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

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('place_id', null, ['property_path' => 'placeId'])
        ->add('latitude')
        ->add('longitude')
        ->add('description')
    ;
    // [...] See below examples [...]
}

My tries

EventListener

$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event)
    {
//      $event->setData($this->locationRepository->find(19));
//      $event->getForm()->setData($this->locationRepository->find(17));
//      /** @var Location $value */
        $value = $event->getData();
//      var_dump(1, $value, $event->getForm()->getData()->getId());
        $location = null;
        if ($value)
        {
            $location = $this->locationRepository->findOneBy(['latitude' => $value->getLatitude(), 'longitude' => $value->getLongitude()]);
//          var_dump('Location found', $location->getId());
            if(!$location)
            {
                var_dump('-------------------- NEW LOCATION ----------------------------');
                $location = new Location();
                $location->setLongitude($value->getLongitude());
                $location->setLatitude($value->getLatitude());
                $location->setDescription($value->getDescription());
                $location->setPlaceId($value->getPlaceId());
            }
//          $event->setData($location);
//          $event->getForm()->setData($location);
        }
        $event->setData($location);
//      var_dump($location->getId());

It's working perfect - but not when I want to set it to null, because then it's ignored and the past location will be used.

Example {}

{}

OK because the location was not submitted so it's ignored. $clearMissing = false -> $form->submit($request->request->all(), false);

Example {"location": {"latitude": 12.0, "longitude": 99.0}}

{"location": {
    "latitude": 12.0,
    "longitude": 99.0
}}

OK because its creating new entity when lat/log isn't found and it reuses the existing entity when lat/lng is found.

Example {"location": null}

{"location": {
    "latitude": 12.0,
    "longitude": 99.0
}}

Wrong, here I expect that the location is null and not the parent one.

ModelTransformer

class UniqueLocationTransformer implements DataTransformerInterface
{
    private $locationRepository;

    public function __construct(LocationRepository $locationRepository)
    {
        $this->locationRepository = $locationRepository;
    }

    public function transform($value)
    {
        return $value;
    }

    public function reverseTransform($value)
    {
        if ($value)
        {
            $location = $this->locationRepository->findOneBy(['latitude' => $value->getLatitude(), 'longitude' => $value->getLongitude()]);
            if($location)
            {
                return $location;
            }
            $location = new Location();
            $location->setLongitude($value->getLongitude());
            $location->setLatitude($value->getLatitude());
            $location->setDescription($value->getDescription());
            $location->setPlaceId($value->getPlaceId());
        }
        return $value;
    }
}

It's working fine, too but even the problem with setting null.

Example {}

{}

OK because the location was not submitted so it's ignored. $clearMissing = false -> $form->submit($request->request->all(), false);

Example {"location": {"latitude": 12.0, "longitude": 99.0}}

{"location": {
    "latitude": 12.0,
    "longitude": 99.0
}}

OK because its creating new entity when lat/log isn't found and it reuses the existing entity when lat/lng is found.

Example {"location": null}

{"location": {
    "latitude": 12.0,
    "longitude": 99.0
}}

Wrong, here I expect that the location is null and not the parent one.

Doctrine prePersist listener

Here the problem is that it's not called for updates and it's not so good because its out of the type scope.

Update 17.7. 18:03 (gmt+2)

$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event)
    {
        $value = $event->getData();
        $location = null;
        if (is_array($value) && array_key_exists('latitude', $value))
        {
            $location = $this->locationRepository->findOneBy(['latitude' => $value['latitude'], 'longitude' => @$value['longitude'] || 0]);
            if(!$location)
            {
                $location = new Location();
                $location->setLongitude(array_key_exists('longitude', $value) ? $value['longitude'] : null);
                $location->setLatitude(array_key_exists('latitude', $value) ? $value['latitude'] : null);
                $location->setDescription(array_key_exists('description', $value) ? $value['description'] : null);
                $location->setPlaceId(array_key_exists('place_id', $value) ? $value['place_id'] : null);
            }
        }
        var_dump($location);
        $event->setData($location);
    });

I changed the event listener now so that its listening on PRE_SUBMIT and added option required which is set to false.

But then I'm getting an error when submitting something valid.

Request:

{"location": {"latitude": 13, "longitude": 14}}

Response:

{
  "location": [
    "This value is not valid."
  ]
}

:(