My Symfony model has a field which is boolean but also accepts NULL so effectively is a tri-state value.
How can I write a widget for this? Symfony auto-generates a sfWidgetFormCheckbox but that
can not be set to NULL.
I tried a sfWidgetFormChoice with but I had to specify the values as strings to get them work:
$this->setWidget('wt', new sfWidgetFormChoice(array(
'choices' => array(true => 'true', false => 'false', null => 'null')
)));
It works for storing values but whenever I save a "false" value, the select jumps back to 'null'. I tried several combinations of 'false', '0', '' etc. but got nothing to work in all three cases.
Any ideas?
An example right from the docs:
class sfWidgetFormTrilean extends sfWidgetForm
{
public function configure($options = array(), $attributes = array())
{
$this->addOption('choices', array(
0 => 'No',
1 => 'Yes',
'null' => 'Null'
));
}
public function render($name, $value = null, $attributes = array(), $errors = array())
{
$value = $value === null ? 'null' : $value;
$options = array();
foreach ($this->getOption('choices') as $key => $option)
{
$attributes = array('value' => self::escapeOnce($key));
if ($key == $value)
{
$attributes['selected'] = 'selected';
}
$options[] = $this->renderContentTag(
'option',
self::escapeOnce($option),
$attributes
);
}
return $this->renderContentTag(
'select',
"\n".implode("\n", $options)."\n",
array_merge(array('name' => $name), $attributes
));
}
}
Related
I try to create form with html select element using EntityType. I must get values by some condition and by this condition necessary default value not select from database. So i get all options values, without one, that must be a default value. So i try to find a way to put this value to select. What i tried...
Set value in form:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $growing, ['user_id' => $user_id]);
$tableForm->get('garden')->setData($garden);
$form = $tableForm->createView()
Then i tried to set data in entity:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$growing->setGarden($garden);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $growing, ['user_id' => $user_id]);
$form = $tableForm->createView()
Then i tried to set default select value in form_builder using 'data' attribute:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $grow, [
'user_id' => $user_id,
'selected_choice' => $garden
]);
$form = $tableForm->createView();
Form_builder code:
class GrowingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('garden', EntityType::class , [
'class' => 'FarmBundle\Entity\Garden',
'query_builder' => function (GardenRepository $gr) use ($options) {
return $gr->queryFreeGardens($options['user_id']);
},
'attr' => [
'data-type' => 'text',
'class' => 'table-select',
'disabled' => true
],
'required' => false,
'data' => $options['selected_choice']
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FarmBundle\Entity\Growing',
'selected_choice' => null,
'user_id' => null
));
}
}
And code of query for query builder:
class GardenRepository extends \Doctrine\ORM\EntityRepository
{
public function queryFreeGardens($user_id)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.growing', 'grow')
->where('grow.plantDate is NULL')
->orWhere('grow.endDate is not NULL')
->andWhere('g.user = :user_id')
->orderBy('g.name')
->setParameter('user_id', $user_id);
return $qb;
}
}
And all of this 3 methods not works. Result is one, if entity not get for query in query builder, i cant set this entity. If i will set entity as default value, that was in query builder all will works fine.
How can i solve this problem?
try this
in controller:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $grow, [
'user_id' => $user_id,
'additional_id' => 7
]);
$form = $tableForm->createView();
in form:
class GrowingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('garden', EntityType::class , [
'class' => 'FarmBundle\Entity\Garden',
'query_builder' => function (GardenRepository $gr) use ($options) {
return $gr->queryFreeGardens($options['user_id'], $options['additional_id');
},
'attr' => [
'data-type' => 'text',
'class' => 'table-select',
'disabled' => true
],
'required' => false
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FarmBundle\Entity\Growing',
'additional_id' => null,
'user_id' => null
));
}
}
in repository:
class GardenRepository extends \Doctrine\ORM\EntityRepository
{
public function queryFreeGardens($user_id, $additional_id)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.growing', 'grow')
->where('grow.plantDate is NULL')
->andWhere('g.id = :additional_id')
->orWhere('grow.endDate is not NULL')
->andWhere('g.user = :user_id')
->andWhere('g.id = :additional_id')
->orderBy('g.name')
->setParameter('user_id', $user_id)->setParameter('additional_id', $additional_id);
return $qb;
}
}
maybe you will need to adjust your repository method to retrieve values in right way. There are or clause, you should add this additional id to both branches of your or clause. The main idea is to retrieve you selected object too.
I have a problem when a i try to insert record into table like this .
$data = array();
foreach($input['user_id'] as $key => $user_id) {
$data[$key]['user_id'] = $user_id;
}
foreach($input['start_on'] as $key => $start_on) {
$data[$key]['start_on'] = $start_on;
}
$this->validate($request, [
'time_start' => 'required|date',
'time_end' => 'required|date|after:time_start',
]);
$rules = [
'start_on' => 'required|date|after:time_start|before:time_end|different:start_on'
];
// Iterate and validate each question
foreach ($data as $value)
{
$validator = Validator::make( $value, $rules );
// if ($validator->fails()) return $validator->messages()->all();
}
I want to check start_on not equals another start_on.So what can i do?
I'm using jQueryUI autocomplete to get a list of cities. I'm using a json array to display cities, but I just can't get the list to appear.
here is my Ajax function :
/**
* #Route("/ajax", name="ajax")
*/
public function ajaxAction(Request $request)
{
$value = $request->get('term');
$em = $this->getDoctrine()->getEntityManager();
$branches = $em->getRepository('AOFVHFlyBundle:City')
->findByName($value);
// Get branches by user if non-admin
$json = array();
foreach ($branches as $branch) {
$json[] = array(
'label' => sprintf('%s (%s)', $branch['name'], $branch['departement']),
'value' => $branch['id']
);
}
$response = new Response();
// better way to return json - via header?
$response->setContent(json_encode($json));
return $response;
}
and here is the form input :
->add('reqdeparture', 'genemu_jqueryautocompleter_entity', array(
'class' => 'AOFVH\FlyBundle\Entity\City',
'property' => 'name',
'route_name' => 'ajax',
'attr' => array(
'placeholder'=>'Départ',
'autocomplete' => 'on')
))
Whatever I type in the input, nothing appears.
Any idea why ? (at first I thought it was because collection was too big, but I barely have 50 cities)
You can use JsonResponse object. The Content-Type header will be 'application/json'. Symfony manage the chars to escape automatically and others conditions to return a JSON normalized correctly.
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* #Route("/ajax", name="ajax")
*/
public function ajaxAction(Request $request)
{
$value = $request->get('term');
$branches = $this->get("doctrine.orm.default_entity_manager")
->getRepository("NamespaceMyBundle:Entity")
->findByName($value);
$json = array();
foreach ($branches as $branch) {
$json[] = array(…);
}
return new JsonResponse($json);
}
I recently started learning ZF2 and hope someone can help me with this.
I am working my way through Rob Allen's Zend Framework 2 Tutorial (many thanks to #rob-allen).
Also I use a solution of #AlloVince and #Maciej How to set db adapter to Validator RecordExists in Zend Framework 2 (many thanks to both authors for it) and I confused because didn't to use this solution in editAction.
I see Fatal error: Call to a member function get() on a non-object in 'adapter' => $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter').
1) In the Module.php added
public function getServiceConfig()
{
return array(
'invokables' => array(
'RegionModel' => 'FcLibraries\Model\Region', //<-- added it
),
'factories' => array(
'FcLibraries\Model\RegionTable' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new RegionTable($dbAdapter);
return $table;
},
),
);
}
2) In the Region.php added
/**
* #var
*/
protected $serviceLocator;
/**
* #param \Zend\ServiceManager\ServiceLocatorInterface $serviceLocator
* #return Library
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
/**
* #return \Zend\ServiceManager\ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->serviceLocator;
}
and
$inputFilter->add($factory->createInput(array(
'name' => 'name',
'required' => true,
'filters' => $this->_filters,
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 30,
),
),
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => $this->table,
'field' => 'name',
//'exclude' => array(
// 'field' => 'id',
// 'value' => $this->id
//),
'adapter' => $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter'),
),
),
),
)));
3) In the RegionController.php in addAction using
$model = $this->getServiceLocator()->get('RegionModel');
instead of $model = new Region();.
This works fine for addAction, But I can not understand how I should use it in editAction.
My
public function editAction()
{
$id = (int)$this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('zfcadmin/region', array(
'action' => 'add'
));
}
$data = $this->getRegionTable()->get($id);
$form = new RegionForm();
$form->bind($data);
$form->get('submitBtn')->setAttribute('value', 'Save');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($data->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getRegionTable()->save($form->getData());
return $this->redirect()->toRoute('zfcadmin/regions');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
My RegionTable has the following code:
/**
* #param \Zend\Db\Adapter\Adapter $adapter
*/
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
$this->resultSetPrototype = new ResultSet();
$this->resultSetPrototype->setArrayObjectPrototype(new Region());
$this->initialize();
}
public function get($id)
{
$id = (int)$id;
$rowSet = $this->select(array('id' => $id));
$row = $rowSet->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
Many thanks to all who will answer my question.
Best regards, Ruslan.
Instead of using the form filter from the entity which you gathered from the table you should instantiate a new entity via the service manager to use the database adapter.
You have a few options:
Move the input filter to its own class and instantiate via the service manager so your database adapter is injected.
Change the prototype object in the table gateway factory to be instantiated via the service manager factory.
Instantiate a separate entity via the service manager and get the input filter from there.
I personally would go for option 1 as it separates the code better.
Some examples:
Option 1 (my choice):
This involves moving the filter to its own file and class, creating a factory for it whilst injecting the database adapter. We will then, in the controller, get the filter via the service manager and apply the filter to the form.
So first move your filter to a file in ModName\src\ModName\Form\RegionFilter.php, obviosly replacing ModName with your module name.
and change the code to like so:
<?php
namespace Region\Form;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Zend\Db\Adapter\Adapter;
class RegionFilter implements InputFilterAwareInterface {
/**
* #var inputFilter
*/
protected $inputFilter;
/**
* #var Database Adapter
*/
protected $dbAdapter;
/**
* #param \Zend\InputFilter\InputFilterInterface $inputFilter
* #throws \Exception
*/
public function setInputFilter(InputFilterInterface $inputFilter) {
throw new \Exception("Not used");
}
/**
* #param \Zend\Db\Adapter $dbAdapter
*/
public function __construct(Adapter $dbAdapter) {
$this->dbAdapter = $dbAdapter;
}
/**
*
* #return Zend\Db\Adapter
*/
public function getDbAdapter() {
return $this->dbAdapter;
}
/**
* #return \Zend\InputFilter\InputFilter
*
* Get the input filter (build it first)
*/
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'name',
'required' => true,
'filters' => $this->_filters,
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 30,
),
),
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => $this->table,
'field' => 'name',
//'exclude' => array(
// 'field' => 'id',
// 'value' => $this->id
//),
'adapter' => $this->getDbAdapter(),
),
),
),
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
?>
You would then create a factory like so in Module.php:
public function getServiceConfig()
{
return array(
'factories' => array(
'ModName\Form\RegionFilter' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
return new RegionFilter($dbAdapter);
},
),
);
}
And finally in your controller, just do the following:
if ($request->isPost()) {
$filter = $this->getServiceLocator()->get('ModName\Form\RegionFilter');
$form->setInputFilter($filter->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getRegionTable()->save($form->getData());
return $this->redirect()->toRoute('zfcadmin/regions');
}
}
Option 2:
This involves constructing your table with an instance of Region injected. Then you can set the prototype to this.
So in your table construct:
public function __construct(Adapter $adapter, Region $region)
{
$this->adapter = $adapter;
$this->resultSetPrototype = new ResultSet();
$this->resultSetPrototype->setArrayObjectPrototype($region);
$this->initialize();
}
And then your factory:
public function getServiceConfig()
{
return array(
'invokables' => array(
'RegionModel' => 'FcLibraries\Model\Region',
),
'factories' => array(
'FcLibraries\Model\RegionTable' => function ($sm) {
$region = $sm->get('RegionModel');
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new RegionTable($dbAdapter,$region);
return $table;
},
),
);
}
You should be able to leave the rest of the code as is. Eg the controller. Now I have not tested this method so I'm not 100% it will work, but I think it should. The other two methods I have used previously myself.
Option 3 (the simplest):
This involves getting a separate region model via the service manager and using that to apply the input filter to the form.
public function editAction()
{
$id = (int)$this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('zfcadmin/region', array(
'action' => 'add'
));
}
$data = $this->getRegionTable()->get($id);
$form = new RegionForm();
$form->bind($data);
$form->get('submitBtn')->setAttribute('value', 'Save');
$request = $this->getRequest();
if ($request->isPost()) {
$region = $this->getServiceLocator()->get('RegionModel');
$form->setInputFilter($region->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getRegionTable()->save($form->getData());
return $this->redirect()->toRoute('zfcadmin/regions');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
I have not tested the code but you should get the gist. Any questions just ask.
For doing the validation for "Username already exists or not", Do the following simple way of Service Manager config settings like:
// config/autoload/global.php
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2tutorial;host=localhost',
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => function ($serviceManager) {
$adapterFactory = new Zend\Db\Adapter\AdapterServiceFactory();
$adapter = $adapterFactory->createService($serviceManager);
\Zend\Db\TableGateway\Feature\GlobalAdapterFeature::setStaticAdapter($adapter);
return $adapter;
}
),
)
);
and add the following array into getInputFilter():
array(
'table' => 'users',
'field' => 'username',
'adapter' => \Zend\Db\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter();
)
I would like to use a conditional statement when creating a form in Symfony.
I am using a choice widget in general case. If the user selects the option "Other", I would like to display an additional text box widget. I suppose this can be done in javascript, but how can I still persist the data from 2 widgets into the same property in my entity?
I have this so far:
$builder->add('menu', 'choice', array(
'choices' => array('Option 1' => 'Option 1', 'Other' => 'Other'),
'required' => false,
));
//How to add text box if choice == Other ????
I was planing to use a DataTransfomer, but on 2 widgets??
I recommend to build a custom type for that, for example ChoiceOrTextType. To this type you add both the choice (named "choice") and the text field (named "text").
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ChoiceOrTextType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('choice', 'choice', array(
'choices' => $options['choices'] + array('Other' => 'Other'),
'required' => false,
))
->add('text', 'text', array(
'required' => false,
))
->addModelTransformer(new ValueToChoiceOrTextTransformer($options['choices']))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array('choices'));
$resolver->setAllowedTypes(array('choices' => 'array'));
}
}
As you already guessed, you also need a data transformer, which can be quite simple:
use Symfony\Component\Form\DataTransformerInterface;
class ValueToChoiceOrTextTransformer implements DataTransformerInterface
{
private $choices;
public function __construct(array $choices)
{
$this->choices = $choices;
}
public function transform($data)
{
if (in_array($data, $this->choices, true)) {
return array('choice' => $data, 'text' => null);
}
return array('choice' => 'Other', 'text' => $data);
}
public function reverseTransform($data)
{
if ('Other' === $data['choice']) {
return $data['text'];
}
return $data['choice'];
}
}
Now only make the "menu" field a field of that type.
$builder->add('menu', new ChoiceOrTextType(), array(
'choices' => array('Option 1' => 'Option 1', 'Option 2' => 'Option 2'),
'required' => false,
));