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 the machine entity:
- id
- machine name
- monitor id
And the monitor entity:
- id
- serialnumber
- ...
For a new machine, the customer needs to fill the form with:
- machine name
- serialnumber of attached monitor
- ...
Now, if I make a form with, as data backing, a machine entity, I have no 'field' in which to ask for the serial number. Symfony doesn't allow it, since the backing entity doesn't have a field named 'serial number'.
How can I:
- ask for the serial number while there is no such field in the backing entity
- if I get the serial number, how do I link it up with it's internal id to persist with the entity once the data is validated and bound
I figure I can:
- make a form without an object behind it, as described by @weaverryan in this excellent article: http://knplabs.com/en/blog-csi/symfony-validators-standalone - this way I do the persisting myself, but I need separate constraints for my custom form, and for my machine entity, which is a pity.
- provide some kind of link so that Symfony knows where to get the fact that a serialnumber field exists, and what it's constraints are. Perhaps by defining a relation? Tbh I was hoping to avoid relations in my entity code, because I've seen quite a few problems arise from it on the mailinglist, but maybe I have no choice :-)
- anything else?
I hope I'm explaining this right. I think many have had to solve this. I think I'm just looking over some very standard Symfony functionality, just because I'm not sure what it's called :-)
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.
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!
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:
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.
This class transforms the serial to an actual Monitor and an actual Monitor to a serial
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