When using Symfony2.1 forms, at what point should I bind custom data (an object) based on form values to my form's object? - symfony-forms

Given the following scenario, where should I put logic to bind Department to Review:
Entities:
Dealership (has many departments)
Department (has one type)
DepartmentType
Review (has one dealership and one department)
On my ReviewForm I need the user to be able to select a Dealership and a DepartmentType, and then in some form of callback or pre/post bind, work out from them which Department to bind to the Review.
I also need this to happen prior to validation so that I can validate that the Department is child of the Dealership.
Note: Review relates to both Dealership and Department when it could just relate to Department to ease traversal and other logic I have going on.
There are two approaches I've tried so far but reached deadends / confusion.
DataTransformer on the DepartmentType on the form, not sure I understand this properly, my transform / reverseTransform methods were getting passed in the Review object, not the field object.
PRE_BIND, happens before validation but I only have raw data to work with, no objects
POST_BIND, happens after validation :(
For the final step of validation of the relationship I have a relatively simple validator that should do the job, but I'm not sure at what point I am meant to bind data to the object like this. Any pointers?

As validation is also done in a POST_BIND listener, you could simply add your POST_BIND listener with a higher priority than the validation listener (i.e. anything > 0).
If you're writing a listener:
$builder->addEventListener(FormEvents::POST_BIND, $myListener, 10);
and if you're writing a subscriber:
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_BIND => array('postBind', 10),
);
}
public function postBind(FormEvent $event)
{
...
}

I would go with a standard (ie: non-Doctrine) choice type containing a choice to represent each DepartmentType.
Then use a DataTransformer to turn the selected option into the relevant type, and vice versa.
Your custom FormType should end up looking something like this:
class Department extends AbstractType
{
private $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new DepartmentToTypeTransformer($this->em);
$builder->addViewTransformer($transformer, true);
$builder->getParent()->addEventListener(FormEvents::PRE_BIND, function($event) use ($transformer) {
$data = (object) $event->getData();
$transformer->setDealership($data->dealership);
});
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'department';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$choices = array();
foreach ($this->em->getRepository('AcmeDemoBundle:DepartmentType')->findAll() as $type) {
$choices[$type->getId()] = (string) $type;
}
$resolver->setDefaults(array(
'choices' => $choices,
'expanded' => true
));
}
}
Note the passing of the Dealership into the DataTransformer for use in the transformation.
And DataTransformer something like this:
class DepartmentToTypeTransformer implements DataTransformerInterface
{
private $em;
private $dealership;
public function __construct($em)
{
$this->em = $em;
}
public function transform($department)
{
if (null === $department) {
return $department;
}
return $department->getType()->getId();
}
public function reverseTransform($type)
{
if (null === $type) {
return $type;
}
return $this->em->getRepository('AcmeDemoBundle:Department')->findOneBy(array(
'dealership' => $this->getDealership(),
'type' => $type
));
}
public function getDealership() {
return $this->dealership;
}
public function setDealership($dealership) {
$this->dealership = $dealership;
return $this;
}
}
Your confusion regarding what is being passed to your transformer is most likely caused by the transformer you're binding being appended to pre-existing behaviour, try adding true as the second parameter to addViewTransformer:
$transformer = new DepartmentToTypeTransformer($this->em);
$builder->addViewTransformer($transformer, true);
From the docs:
FormBuilder::addViewTransformer(
DataTransformerInterface $viewTransformer,
Boolean $forcePrepend = false
)
Appends / prepends a transformer to the view transformer chain.

Related

zf2 factories for populating controller with domain objects

I have two different ways of loading my controller with it's domain model. I'd be interested in hearing which is better.
First method - traditional.
A controller factory injects the required service into the controller constructor. Within the controller action, the model is loaded based on the request param:
ClientAppointmentsControllerFactory.php
class ClientAppointmentsControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
$serviceManager = $serviceLocator->getServiceLocator();
$controller = new ClientAppointmentsController($serviceManager->get('Service\ClientAppointments'));
return $controller;
}
}
ClientAppointmentsController.php
class ClientAppointmentsController extends AbstractActionController
{
public function __construct(AppointmentFactory $appointmentFactory){
$this->appointmentFactory = $appointmentFactory;
}
public function indexAction() {
$viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
$appointments = $this->appointmentFactory->getClientAppointments($this->params()->fromRoute('clientId'));
$viewModel->setVariables([
'appointments' => $appointments
]);
return $viewModel;
}
}
Second Method - Accessing request/route parameters in factory
This seems a bit cleaner to me, as now the controller has no dependency on the service layer, and just expects (from whatever source) an array of loaded objects to pass to the view. I think this still fits the definition of a factory, since it is creating the controller with it's required dependencies, although is now actively creating them instead of passing this onto the controller to do:
ClientAppointmentsControllerFactory.php
class ClientAppointmentsControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
$getRequestParam = function($param) use($serviceLocator){
$serviceManager = $serviceLocator->getServiceLocator();
$request = $serviceManager->get('Request');
$router = $serviceManager->get('Router');
$match = $router->match($request); // \Zend\Mvc\Router\RouteMatch
$result = $match->getParam($param);
return $result;
};
$serviceManager = $serviceLocator->getServiceLocator();
$clientService = $serviceManager->get('Service\ClientAppointments');
$appointments = $clientService->fetchByClientId($getRequestParam('clientId));
$controller = new ClientAppointmentsController($appointments);
return $controller;
}
}
ClientAppointmentsController.php
class ClientAppointmentsController extends AbstractActionController
{
/**
* #param Array $appointments Array of Appointment objects
*/
public function __construct(Array $appointments){
$this->appointments = $appointments
}
public function indexAction() {
$viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
$viewModel->setVariables([
'appointments' => $this->appointments
]);
return $viewModel;
}
Which is better?
(I also have an idea of a mutable factory floating around.)
IMO, the second is not good at all, because it mixes creation logic with business logic. This means a business logic error will preclude a factory from working.
The first one is better, but not good, because you have got now business logic in the controller.
I would suggest moving business logic into a business model OR to a controller plugin.

How to get entity manager in form type

How could I get entity manager when building forms?
I would like to search results from the database and build the choices for choicetype.
I know I could use entitytype instead but in this situation I want to record string in database than an object.
And also I need to add some more options as well.
Thank you.
In Symfony 3.2 (and possibly others, I'm not sure about 3.1, but it is probably the same), the $this->createForm() method needs a string as the first parameter, and cannot take a form object.
Add a configureOptions method to your form class:
class YourFormType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'entityManager' => null,
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Entity Manager is set in: $options['entityManager']
}
}
Then get the form in your controller like so, passing in the Entity Manager:
$form = $this->createForm(
YourFormType::class,
$yourEntity,
[
'entityManager' => $this->getDoctrine()->getManager(),
]
);

ZF2 nested data validation

I'm trying make to work my validation. I have data posted to controller in the format like this:
[
'property' => 'value',
'nested_property' => [
'property' => 'value',
// ...
]
]
I have divided fields/filters and form into different classes and just gather it together in the Form's controller that looks like that:
public function __construct($name, $options)
{
// ...
$this->add(new SomeFieldset($name, $options));
$this->setInputFilter(new SomeInputFilter());
}
But it doesn't work properly, looks like it just ignores nested array (or ignores everything). What have I missed?
Thank you.
You need to set up your inputfilter like the way you've setup your forms including the fieldsets if you use the InputFilter class.
So when you've got a structure like:
MyForm
1.1 NestedFieldset
1.2 AnotherFieldset
Your inputfilters need to have the same structure:
MyFormInputFilter
1.1 NestedFielsetInputFilter
1.2 AnotherFieldsetInputFilter
Some example code:
class ExampleForm extends Form
{
public function __construct($name, $options)
{
// handle the dependencies
parent::__construct($name, $options);
$this->setInputFilter(new ExampleInputFilter());
}
public function init()
{
// some fields within your form
$this->add(new SomeFieldset('SomeFieldset'));
}
}
class SomeFieldset extends Fieldset
{
public function __construct($name = null, array $options = [])
{
parent::__construct($name, $options);
}
public function init()
{
// some fields
}
}
class ExampleInputFilter extends InputFilter
{
public function __construct()
{
// configure your validation for your form
$this->add(new SomeFieldsetInputFilter(), 'SomeFieldset');
}
}
class SomeFieldsetInputFilter extends InputFilter
{
public function __construct()
{
// configure your validation for your SomeFieldset
}
}
So the important part of configuring your inputFilter for these situations is that you need to reuse the name of your fieldset when using: $this->add($input, $name = null) within your InputFilter classes.

How to pass values - controller to controller?

I am using MVC and want to pass values, controller to controller
My code:
public ActionResult Index()
{
List<string> SportsName = new List<string>();
var sport = Db.Universities.Where(ud => ud.Contact.UserName.ToLower().Trim() == User.Identity.Name.ToLower().Trim()).SingleOrDefault();
var spt = Db.Departments.Where(i => i.UniversityID == sport.UniversityID && i.DepartmentCodeID == 4).SingleOrDefault();
unvId = int.Parse(sport.UniversityID.ToString());
List<Sport> dept = Db.Sports.Where(s => s.DepartmentID == spt.DepartmentID).ToList();
foreach (var sname in dept.ToList())
{
var name = Db.SportsCodes.Where(s => s.SportsCodeID == sname.SportsCodeID).First();
SportsName.Add(name.SportsName);
}
ViewBag.SportsName = SportsName;
return View();
}
public ActionResult Create(string sports)
{
ViewBag.SportsName = sports;
int s = unvId;
return View();
}
I want the 'sport' value in create action also. How to get the value of 'sport' in create action?
What I guess from your question is You want to pass the SportsName from Index to Create.
From Index View (.cshtml) when you call the Create method through AJAX call, pass the value of the ViewBag.Sports as a parameter.
For Example :
$('#Link').click(function () {
$.ajax({
url: http://localhost/Sports/Create,
type: 'GET',
data: {
sports: "#ViewBag.SportsName"
},
success: function () {
},
error: function () {
}
});
[Note : Here it is considered that name of your controller is Sports]
This answers to your question.
You have a number of options. If only individual action methods need that value, fetch the value in each action method that needs it:
public ActionResult SomeMethod()
{
var sport = Db.Universities.Where(ud => ud.Contact.UserName.ToLower().Trim() == User.Identity.Name.ToLower().Trim()).SingleOrDefault();
// ...
}
If the repeated code seems unsightly, abstract it to a helper method:
public ActionResult SomeMethod()
{
var sport = GetSport();
// ...
}
private SomeType GetSport()
{
return Db.Universities.Where(ud => ud.Contact.UserName.ToLower().Trim() == User.Identity.Name.ToLower().Trim()).SingleOrDefault();
}
If it should be accessible anywhere in the class and is logically a class-level member, make it a class-level member:
private SomeType sport = Db.Universities.Where(ud => ud.Contact.UserName.ToLower().Trim() == User.Identity.Name.ToLower().Trim()).SingleOrDefault();
public ActionResult SomeMethod()
{
// sport is accessible here
// ...
}
Though now that I notice it, this begs the question "What is Db"? If that's a database context then it looks like you've expanded the scope of it beyond what it really should be. Database contexts and connections should be kept in as small a scope as possible. Each method that needs one should create it, use it, and destroy it. Sharing them at a larger scope without explicitly knowing what you're doing is inviting a whole host of problems. In this case, the class-level member would be initialized in the constructor:
private SomeType sport;
public YourController()
{
using (var Db = BuildYourDBContext())
sport = Db.Universities.Where(ud => ud.Contact.UserName.ToLower().Trim() == User.Identity.Name.ToLower().Trim()).SingleOrDefault();
}
public ActionResult SomeMethod()
{
// sport is accessible here
// ...
}
Any way you look at it, the point is that being a controller doesn't really make a difference. The controller is an object like any object in an object-oriented system. It shares members exactly the same way.

Where can i find an example of jqGrid being used as part of form for posting?

I have a form where I have a bunch of textbox and dropdowns. I now need to add another array of sub objects and include that as part of the my form post.
I was going to hand roll this as an html table but i thought that i could leverage jqGrid. What is the best way I can use jqGrid locally to add data and then have that included in the form post? The reason that i need jqGrid to act locally is that these are subrecords as part of the larger form so I can't post the jqGrid rows until the larger form is posted (so i have an Id to join these rows with)
So for example, if my post was an Order screen (with textboxes for date, instructions, etc) and now i want to have a grid that you can add products into the order. You can have as many rows as you want . .)
my backend is asp.net-mvc if that helps with any suggestions.
If you use form editing you can extend the postdata in many ways. The most simple one is the usage of onclickSubmit callback:
onclickSubmit: function (options, postData) {
return {foo: "bar"};
}
If you use the above callback then the data which will be post to the server will be extended with the parameter foo with the string value "bar".
Another possibility is the usage of editData option of editGridRow. The best way is to use properties of editData defined as function. In the way the funcion will be called every time before posting of data.
For example the following code
$("#grid").jqGrid("navGrid", "#pager", {}, {
editData: {
foo: function () {
return "bar";
}
},
onclickSubmit: function (options, postData) {
return {test: 123};
}
});
will add foo=bar and test=123 to the parameters which will be send to the server.
The next possibility will be to use serializeEditData. The callback gives you full control on the data which will be sent to the server.
I am using the method of serialization as Oleg suggested.
view
$( "#Save" ).click( function ( e )
{
e.preventDefault();
var griddata= $( "#list" ).getRowData();
var model = {
grid: griddata
};
var gridVal = JSON.stringify( model );
//Now set this value to a hiddenfield in the form and submit
$('#hiddenGridDta').val(gridVal );
$( 'form' ).submit();
});
And in the controller, deserialize the values using Newtonsoft.json.jsonconvert().
public ActionResult SaveTest(TestClass test)
{
testViewModel myGrid = JsonConvert.DeserializeObject<testViewModel>(test.hiddenGridDta);
................
}
testViewModel class
public class testViewModel
{
public IEnumerable<TestGrid> grid { get; set; }
}
TestGrid class
public class profileGrid
{
//fields in the jqgrid (should use the same name as used in *colModel:* of jqgrid)
public int x
{
get;
set;
}
public int y
{
get;
set;
}
.......
}

Resources