Following the Symfony 3.0 manual on testing Form Types, I followed the example for testing forms with dependencies as follows;
<?php
namespace Tests\AppBundle\Form;
use AppBundle\Form\Type\Database\OfficeAssignType;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class OfficeTypeTest extends TypeTestCase
{
private $entityManager;
protected function setUp()
{
// mock any dependencies
$this->entityManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
parent::setUp();
}
protected function getExtensions()
{
// create a type instance with the mocked dependencies
$office = new OfficeType($this->entityManager);
return array(
// register the type instances with the PreloadedExtension
new PreloadedExtension(array($office), array()),
);
}
public function testSubmitValid()
{
$formData = array(
'name' => 'new test office',
'address1' => 'Test new office address',
'city' => 'Test office city',
'phone' => 'testoffice#stevepop.com',
'country' => '235',
'currrency' => '1',
);
// Instead of creating a new instance, the one created in
// getExtensions() will be used
$form = $this->factory->create(OfficeType::class);
// submit data to form
//$form->submit($formData);
//$this->assertTrue($form->isSynchronized());
}
}
When I run the tests, I get the following error
Argument 1 passed to Symfony\Bridge\Doctrine\Form\Type\DoctrineType::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, none given, called in /www/stevepop.com/symfony/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php on line 85 and defined
The OfficeType is below;
namespace AppBundle\Form\Type\Database;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class OfficeType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class, array(
'required' => true,
));
$builder->add('address1', TextType::class, array(
'required' => true,
));
$builder->add('country', EntityType::class, array(
'class' => 'AppBundle:Country',
'attr' => array(
'class' => 'input-sm',
),
'required' => true,
'placeholder' => "Non selected",
));
$builder->add('currency', EntityType::class, array(
'class' => 'AppBundle:Currency',
'attr' => array(
'class' => 'input-sm',
),
'required' => true,
'placeholder' => "Non selected",
));
}
I am not sure what this means to be honest and will appreciate help from anyone who has done FormType unit tests in Symfony 2.8/3.0.
Thank you.
Try to adding this to your tests:
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Component\Form\Extension\Core\CoreExtension;
class OfficeType extends AbstractType {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
public function setUp()
{
$this->em = DoctrineTestHelper::createTestEntityManager();
parent::setUp();
}
protected function getExtensions()
{
$manager = $this->createMock('Doctrine\Common\Persistence\ManagerRegistry');
$manager->expects($this->any())
->method('getManager')
->will($this->returnValue($this->em));
$manager->expects($this->any())
->method('getManagerForClass')
->will($this->returnValue($this->em));
return array(
new CoreExtension(),
new DoctrineOrmExtension($manager),
);
}
// your code...
}
Related
The error here is not explicit, from what I think it is related just to a Type difference between parameters but i have not found the way to resolve this...
The EventListener is receiving 2 parameters but it doesn't provide the information of which of them is the "1" argument with the issue but I think it is the 2nd due to the error says an instance of UserBundle\Form\FormEvent on singular and the first argument is a generic event from the FormEvents class which is on plural.
Error Shown:
Type error: Argument 1 passed to
UserBundle\Form\UserType::UserBundle\Form{closure}() must be an
instance of UserBundle\Form\FormEvent, instance of
Symfony\Component\Form\FormEvent given
<?php
namespace UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
class UserType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username',HiddenType::class)
->add('firstname',TextType::class)
->add('lastname',TextType::class)
->add('email', RepeatedType::class,
array('type' => EmailType::class, 'invalid_message' => 'The email fields must match.','options'
=> array('attr' => array('class' => 'email-field')),'required' => true, 'first_options'
=> array('label' => 'Email'),'second_options' => array('label' => 'Confirm email'),))
->add('password', RepeatedType::class,
array('type' => PasswordType::class, 'invalid_message' => 'The password fields must match.','options'
=> array('attr' => array('class' => 'password-field')),'required' => true, 'first_options'
=> array('label' => 'Password'),'second_options' => array('label' => 'Confirm password'),))
->add('save',SubmitType::class)
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if ($data['firstname'] !== null && $data['lastname'] !== null){
$username = $data['firstname']." ".$data['lastname'];
$data['username'] = $username;
$event->setData($data);
}
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'UserBundle\Entity\User'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'user';
}
}
I have followed the steps given by symfony documentation
You're missing the FormEvent class import.
use Symfony\Component\Form\FormEvent;
Since you did not import it, it used the current namespace for the class (UserBundle\Form\FormEvent). But this class doesn't exist.
I Have been following zf2 guide for blog I have created everything Controller, Factory, Form, Mapper, Model, Service, view etc
In my form I have a select element
$this->add(array(
'type' => 'select',
'name' => 'roleId',
'attributes' => array(
'id' => 'roleId',
'options' => array(
'1' => 'Admin',
'2' => 'Manager',
),
),
'options' => array(
'label' => 'Role',
),
));
Now in this form I want to load the option for the role from the database.
I tried loading the option by creating a simple function, which can be accessed in the element as below, but Am not able to fetch the result. I have already created Controller, Factory, Form, Mapper, Model, Service and view, Where I can do CRUD operation on Role.
$this->add(array(
'type' => 'select',
'name' => 'roleId',
'attributes' => array(
'id' => 'roleId',
'options' => $this->getAllRoles(),
),
'options' => array(
'label' => 'Role',
),
));
public function getAllRoles()
{
$roles = $this->getServiceLocator()->get('Admin\Service\RoleService');
$allRoles = $this->getAllTheRoles();
return $allroles;
}
Can anybody guide me how can I load all the Roles in option as listed in the IndexAction following Blog Post with ID and Name of the Role.
You could create a reusable form element that is pre-populated with the roles. To do so you must register the service with the form element manager in module.config.php.
return [
'form_elements' => [
'factories' => [
'RoleSelect' => 'MyModule\Form\Element\RoleSelectFactory',
],
],
];
There is not need to extend the standard select class as the changes are configuration only. This is best done in a factory class.
namespace MyModule\Form\Element;
use Zend\Form\Element\Select;
class RoleSelectFactory
{
public function __invoke($formElementManager, $name, $rname)
{
$select = new Select('role_id');
$select->setOptions(['label' => 'Role']);
$select->setAttributes(['id' => 'role_id']);
$serviceManager = $formElementManager->getServiceLocator();
$roleService = $serviceManager->get('Admin\Service\RoleService');
$options = [];
foreach($roleService->getAllTheRoles() as $role){
$options[$role->getId()] => $role->getName();
}
$select->setValueOptions($options);
return $select;
}
}
Adding the element within a form can then be updated to use the name of the service we registered.
$this->add([
'name' => 'role_id'
'type' => 'RoleSelect',
]);
One important point to remember is that the form using this element must be created using the $formElementManager->get('FormWithRoleSelect').
Finally found out the simple way to do this, Am really not sure if this is the correct way.
Added the Role service in the User Controller
Code in my userController.php
use Admin\Service\RoleServiceInterface;
class UserController extends AbstractActionController
{
/**
* #var \Admin\Service\UserServiceInterface
*/
protected $userService;
protected $roleService;
protected $userForm;
public function __construct(
UserServiceInterface $userService,
RoleServiceInterface $roleService,
FormInterface $userForm
)
{
$this->userService = $userService;
$this->roleService = $roleService;
$this->userForm = $userForm;
}
public function addAction()
{
$request = $this->getRequest();
$roles = $this->roleService->findAllRoles();
foreach ($roles as $role) {
if($role->getStatus() == 1) {
$allRoles[$role->getId()] = $role->getName();
}
}
$this->userForm->__construct(null, array('roleOptions'=>$allRoles));
}
}
My UserControllerFactory.php
<?php
namespace Admin\Factory;
use Admin\Controller\UserController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserControllerFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
*
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$userService = $realServiceLocator->get('Admin\Service\UserServiceInterface');
$roleService = $realServiceLocator->get('Admin\Service\RoleServiceInterface');
$userInsertForm = $realServiceLocator->get('FormElementManager')->get('Admin\Form\UserForm');
return new UserController(
$userService,
$roleService,
$userInsertForm
);
}
}
Finally the UserForm.php
<?php
namespace Admin\Form;
use Zend\Form\Form;
use Admin\Model\User;
use Zend\Stdlib\Hydrator\ClassMethods;
use Zend\Form\Element\Select;
class UserForm extends Form
{
public function __construct($name = null, $options = array())
{
$roleOptions = array();
if($options) {
$roleOptions = $options['roleOptions'];
}
parent::__construct($name, $options);
$this->setHydrator(new ClassMethods(false));
$this->setObject(new User());
$this->add(array(
'type' => 'hidden',
'name' => 'id'
));
$this->add(array(
'type' => 'select',
'name' => 'roleId',
'attributes' => array(
'id' => 'roleId',
'options' => $roleOptions
),
'options' => array(
'label' => 'Role',
),
));
}
}
This way using service manager I was successfully able to load the data in my for Select option.
Call getAllRoles() inside the controller then You can pass your custom array as parameter for form when you create form object. In form __construct function you can retrieve that array and set like this
'options' => $roles,
I have a problem with collection form type. My entities:
User
use Doctrine\Common\Collections\ArrayCollection;
/**
* #OneToMany(targetEntity="Comment", mappedBy="user")
*/
protected $comments;
public function __construct() {
$this->comments= new ArrayCollection();
}
Comment
/**
* #ManyToOne(targetEntity="User", inversedBy="comments")
* #JoinColumn(name="user_id", referencedColumnName="id")
**/
protected $user;
Formbuilder:
$form = $silex['form.factory']->createBuilder('form', $user)
->add('comments', 'collection', array(
'type' => 'text',
'options' => array(
'required' => false,
'data_class' => 'Site\Entity\Comment'
),
))
->getForm();
and is returned error:
Catchable fatal error: Object of class Site\Entity\Comment could not be converted to string in C:\XXX\vendor\twig\twig\lib\Twig\Environment.php(331) : eval()'d code on line 307 Call Stack
I think that you might struggle to use a text type collection field here, since you want a field which is a collection of complex entities and not just one which is an array of strings.
I would suggest adding a new Form type for the Comment Entity:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class CommentType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('text', 'text', array());
}
public function getName() {
return 'comment';
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'Site\Entity\Comment'
);
}
}
Then in the original Formbuilder, reference this type:
$form = $silex['form.factory']->createBuilder('form', $user)
->add('comments', 'collection', array(
'type' => new CommentType(),
'options' => array(
'required' => false,
'data_class' => 'Site\Entity\Comment'
'allow_add' => true,
'allow_delete' => true
),
))
->getForm();
I've built a form with the silex framework that contains some checkboxes and a textfield. The user has to check at least one checkbox or write something in the textfield. Now I don't know how to validate such a dependency or better where to put the validation logic. I could add constraints to the single fields but how can I implement the dependency that either the checkboxes are validated or the textfield?
This is my validation code in the controller class.
public function validateAction(Request $request, Application $app)
{
$form = $app['form.factory']->create(new ApplicationForm());
$form->bind($request);
if ($form->isValid()) {
return $app->json(array(
'success' => true,
));
}
}
The ApplicationForm class looks like this (simplified):
class ApplicationForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('crafts', 'choice', array(
'choices' => array(
array('drywall' => 'Drywall'),
array('painter' => 'Painter'),
array('plasterer' => 'Plasterer'),
array('carpenter' => 'Carpenter'),
array('electrician' => 'Electrician'),
array('plumber' => 'Plumber'),
array('tiler' => 'Tiler'),
array('bricklayer' => 'Bricklayer'),
),
'multiple' => true,
'expanded' => true,
'required' => false,
))
->add('craftsOther', 'text', array(
'attr' => array('class' => 'textinput', 'placeholder' => 'Other', 'maxlength' => 256),
'constraints' => array(
new Assert\Length(array('max' => 256, 'maxMessage' => $this->_errorMessages['crafts_other_max'])),
),
'required' => false,
));
}
}
Any ideas how to do this in an elegant way?
You can add constraints to the form itself, not just individual fields. You can pass an array of constraints in the options:
Here is a working example that checks both fields are not equal when submitted.
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Silex\Application;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Constraints as Assert;
$app = new Application();
$app['debug'] = true;
$app->register(new \Silex\Provider\FormServiceProvider());
$app->register(new Silex\Provider\TranslationServiceProvider(), array('translator.domains' => array(),));
$app->register(new Silex\Provider\ValidatorServiceProvider());
$app->register(new Silex\Provider\TwigServiceProvider());
class ExampleFormType extends AbstractType
{
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
{
$builder
->add('apple')
->add('banana')
->add('submit', SubmitType::class);
}
public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new Assert\Callback(function (array $data, ExecutionContextInterface $context) {
if ($data['apple'] === $data['banana']) {
$context
->buildViolation('Apple and Banana cannot be the same')
->atPath('apple')
->addViolation();
}
}),
],
]);
}
}
$app->match('/', function (Request $request, Application $app) {
$form = $app['form.factory']->create(ExampleFormType::class)->handleRequest($request);
if ($form->isValid()) {
echo "Valid Submission";
}
return $app['twig']->createTemplate('{{ form(form) }}')->render([
'form' => $form->createView(),
]);
})->method('GET|POST');
$app->run();
I'm using Symfony2 form component to build and validate forms. Now I need to setup validator groups based on a single field value, and unfortunately it seems that every example out there is based on entities - which im not using for several reasons.
Example:
If task is empty, all constraint validators should be removed, but if not, it should use the default set of validators (or a validator group).
In other words, what I'm trying to achieve is making subforms optional, but still be validated if a key field is populated.
Can someone possible give me an example how to configure it?
<?php
namespace CoreBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;
use CoreBundle\Form\Type\ItemGroupOption;
class ItemGroup extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', 'text', array(
'label' => 'Titel',
'attr' => array('class' => 'span10 option_rename'),
'required' => false
));
$builder->add('max_selections', 'integer', array(
'label' => 'Max tilvalg',
'constraints' => array(new Assert\Type('int', array('groups' => array('TitleProvided')))),
'attr' => array('data-default' => 0)
));
$builder->add('allow_multiple', 'choice', array(
'label' => 'Tillad flere valg',
'constraints' => array(new Assert\Choice(array(0,1))),
'choices' => array(0 => 'Nej', 1 => 'Ja')
));
$builder->add('enable_price', 'choice', array(
'label' => 'Benyt pris',
'constraints' => array(new Assert\Choice(array(0,1))),
'choices' => array(0 => 'Nej', 1 => 'Ja'),
'attr' => array('class' => 'option_price')
));
$builder->add('required', 'choice', array(
'label' => 'Valg påkrævet',
'constraints' => array(new Assert\Choice(array(0,1))),
'choices' => array(0 => 'Nej', 1 => 'Ja')
));
$builder->add('options', 'collection', array(
'type' => new ItemGroupOption(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
)
);
$builder->add('sort', 'hidden');
}
public function getName()
{
return 'item_group';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
global $app;
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) use ($app) {
// Get submitted data
$data = $form->getData();
if (count($app['validator']->validateValue($data['title'], new Assert\NotBlank())) == 0) {
return array('TitleProvided');
} else {
return false;
}
},
));
}
}
If you are using 2.1 you may want to have a look at "Groups based on submitted data".
Update
Example using the demo contact page /demo/contact in the default AcmeDemoBundle provided with Symfony Standard Edition :
The form type with conditional validation groups :
namespace Acme\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;
class ContactType extends AbstractType
{
// Inject the validator so we can use it in the closure
/**
* #var Validator
*/
private $validator;
/**
* #param Validator $validator
*/
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', 'email');
$builder->add('message', 'textarea', array(
// Added a constraint that will be applied if an email is provided
'constraints' => new Assert\NotBlank(array('groups' => array('EmailProvided'))),
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// This is needed as the closure doesn't have access to $this
$validator = $this->validator;
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) use ($validator) {
// Get submitted data
$data = $form->getData();
$email = $data['email'];
// If email field is filled it will not be blank
// Then we add a validation group so we can also check message field
if (count($validator->validateValue($email, new Assert\NotBlank())) == 0) {
return array('EmailProvided');
}
},
));
}
public function getName()
{
return 'contact';
}
}
Don't forget to inject the validator service in the form type :
<?php
namespace Acme\DemoBundle\Controller;
//...
class DemoController extends Controller
{
// ...
public function contactAction()
{
$form = $this->get('form.factory')->create(new ContactType($this->get('validator')));
// ...
}
}
As you can see validation of the message field will be triggered only if the email field is filled.
Use a diff tool to catch the differences.