Symfony2 form where the data objects doesn't match exactly what needs to be filled in

前端 未结 2 1920
误落风尘
误落风尘 2021-01-07 00:12

We have a monitoring service where our monitor units keep an eye on certain machines.

I\'m creating a form to register a new machine in Symfony2.

So we have

相关标签:
2条回答
  • 2021-01-07 00:41

    First, you should be using relationship - i.e. Machine has one monitory. I think people have problems with this because when you're used to thinking about things in terms of "id"s and foreign keys, thinking about relationships in a fully object-oriented way is new :).

    So, assuming your Machine is related to Monitor, you can now create a MachineType that embeds a MonitorType (which would be a form with the serialnumber field in it). Then, your constraints for serialnumber on your Monitor class would be used when the compound form is submitted.

    By default, when you bind all of this, it'll create a new Machine object, with a new Monitor object available via $machine->getMonitor(). If you persist, this will be two inserts. However, I'm guessing that you'd rather lookup the monitor by its serialnumber and use that existing monitor, right? To do so is easy, just short-circuit things after you bind your form.

    $form = new MachineType();
    $form->bindRequest($request);
    if ($form->isValid()) {
        $em = $this->getDoctrine()->getEntityManager();
        $machine = $form->getData();
        $serialNumber = $machine->getMonitor->getSerialNumber();
    
        $existingMonitor = $em
            ->getRepository('YourBundle:Monitor')
            ->findOneBy(array('serialNumber' => $serialNumber));
        if ($existingMonitory) {
            $machine->setMonitor($existingMonitor);
        }
    
        $em->persist($machine);
        $em->persist($machine->getMonitor());
        $em->flush();
    
        // ...
    }
    

    So, that's the idea. Biggest thing is that by not using relationships, you're putting yourself at a disadvantage. You see, by embedding these two forms together, you really do have natural access to the serialnumber field.

    Good luck!

    0 讨论(0)
  • 2021-01-07 00:46

    I tackled this problem recently, by creating a custom form type (in my case called a "selector") which has a datatransformer attached to it, which returns the monitor by serial:

    The Machine type:

    class MachineType extends AbstractType {
        public function buildForm(FormBuilder $builder, array $options) {
            $builder->add('name');
            $builder->add('monitor', 'monitor_selector_type');
        }
    
        public function getName() {
            return 'machine;
        }
    }
    

    The monitor selector type (Eventually this will simply be a text field (the default type parent is "text"), in which you will enter the monitor serial. The field will display an error if a non existing serial was entered.

    class MonitorSelectorType extends AbstractType {
        private $doctrine
    
        public function __construct(Registry $doctrine) {
            $this->doctrine = $doctrine;
        }
    
        public function buildForm(FormBuilder $builder, array $options) {
            $transformer = new MonitorToSerialTransformer($this->doctrine->getEntityManager());
            $builder->appendClientTransformer($transformer);
        }
    
        public function getDefaultOptions(array $options) {
            return array(
                'invalid_message'=>'The selected monitor does not exist',
                'error_bubbling'=>false
            );
        }
    
        public function getName() {
            return 'machine;
        }
    }
    

    This class transforms the serial to an actual Monitor and an actual Monitor to a serial

    class MonitorToSerialTransformer {
        private $em
    
        public function __construct(EntityManager $em) {
            $this->em = $em;
        }
    
        public function transform($val) {
            if (null === $val) {
                return '';
            }
            return $val->getSerial();
        }
    
        public function reverseTransform($val) {
            if (!$val) {
                return null;
            }
            $monitor = $this->em->getRepository('xBundle:Monitor')->findOneBy(array('serial' => $val));
            if (null === $monitor) {
                throw new TransformationFailedException('A monitor with serial '.$val.' does not exist!');
            }
            return $monitor;
        }
    }
    

    finally, register the MonitorSelectorType in your service container with id "monitor_selector_type" and tag "form.type" and have doctrine injected to it.

    Now it will be very easy at any random place in your application to use this selector type to select a monitor by ID. No logic has to be added to your Controller at all.

    If you want a new monitor to be created when an unknown serial is entered, you can instantiate it rather than throwing the TransformationFailedException, and even persist it to your entity manager if the machine has no cascing options for the monitor.

    Regards, Bart

    0 讨论(0)
提交回复
热议问题