I have done bunch of projects using ZF2 and Doctrine2. I build my form with as it follows: Create Form class extending Form, then create Fieldsets and set it as a base fieldset, then in the fieldset I add my fields. Within module.php I create factories in formElementConfig for my forms. It was working allways this way until now. I created a new project and suddenly I encounter a problem which I cant find what is going on. This is my code
//module.php
public function getFormElementConfig()
{
return array(
'factories' => array(
'OfferForm' => function($sm) {
$locator = $sm->getServiceLocator();
$form = new \Application\Form\OfferForm();
$form->setServiceLocator($locator);
return $form;
},
)
);
}
//Form
class OfferForm extends Form implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
public function init()
{
$this->setAttributes(array(
'id' => 'offer',
'method' => 'post',
'class' => 'custom',
'enctype' => 'multipart/form-data'
));
$this->setAttribute('method', 'post')
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter());
$this->add(array(
'name' => 'offer',
'type' => 'Application\Form\Fieldset\OfferFieldset',
'options' => array(
'use_as_base_fieldset' => true
)
));
$this->add(array(
'type' => 'Zend\Form\Element\Csrf',
'name' => 'csrf'
));
$this->add(array(
'name' => 'submit',
'attributes' => array(
'id' => 'submit',
'type' => 'submit',
'value' => $this->getServiceLocator()->getServiceLocator()->get('translator')->translate('Submit offer'),
'class' => 'btn btn-info'
)
));
}
....
//Fieldset
class OfferFieldset extends Fieldset implements InputFilterProviderInterface, ServiceLocatorAwareInterface
{
public function init()
{
$this->setHydrator(new ClassMethodsHydrator(false))
->setObject(new Offer());
$this->add(array(
'name' => 'title',
'type' => 'Zend\Form\Element\Text',
'attributes' => array(
'required' => 'required',
'class' => 'form-control',
)
));
....other fileds
}
/**
* #return array
*/
public function getInputFilterSpecification()
{
....
}
}
//Controller
$em = $this->getObjectManager();
$offer = new Offer();
$form = $this->getServiceLocator()->get('FormElementManager')->get('OfferForm');
$form->setHydrator(new DoctrineHydrator($em, 'Application\Entity\Offer'))->bind($offer);
if ($this->request->isPost()) {
$form->setData($this->request->getPost());
if ($form->isValid()) {
var_dump('ok');
}
}
$form->prepare();
return new ViewModel(array(
'form' => $form,
));
This way of doing things is allways working for me until now. If I try to get form element in the Veiw with $this->form->get('offer')->get('title') it says there is no field with name 'title'
One thing that i noticed is when form is called in the controller ($form = $this->getServiceLocator()->get('FormElementManager')->get('OfferForm');) the fieldset method init() where all my fields are set is not invoked.
I tried to dump data there and to die() the application but it did not get in to the method at all.
I can provide more code but I think this is all about building the form
You also need to add your fieldset to the formelementmanager configuration. The manager's initializer will call your fieldset init() method.
Related
i want to render my form in a twig template that overrides html.html.twig
the twig template is named html--resume--myform.html.twig
inside my form class i have this code
public function buildForm(array $form,FormStateInterface $form_state)
{
$form['#theme'] = ['html__resume__myform'];
$form['candidate_name'] = array(
'#type' => 'textfield',
'#title' => t('Candidate Name:'),
'#required' => TRUE,
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save2'),
'#button_type' => 'primary',
);
return $form
}
and inside my resume.module file i have this custom template
function resume_theme() {
return ['html__resume__myform' => ['render element' => 'form']];
}
but my form is not rendered in html--resume--myform.html.twig
or my $form variable is not passed to html--resume--myform.html.twig
html--resume--myform.html.twig is inside templates folder
I am working on a shopping checkout page which requires:
OrderPerson
PersonAddress (delivery)
Order (notes, time etc. not all items are on the form at present to keep it simple)
I have more or less worked out how to put this together however it requires some tidying up.
My form looks as follows:
To make the forms play nicely together I have setup a CheckoutForm that includes the OrderPerson, PersonAddress and Order Fieldsets:
<?php
namespace MyCart\Form;
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterInterface;
class CheckoutForm extends Form
{
public function __construct(
InputFilterInterface $orderFilter,
InputFilterInterface $orderPersonFilter,
InputFilterInterface $personAddressFilter,
$name = null,
$options = array()
) {
parent::__construct('checkout', $options);
$this->orderFilter = $orderFilter;
$this->orderPersonFilter = $orderPersonFilter;
$this->personAddressFilter = $personAddressFilter;
}
public function init()
{
$this->add(
[
'name' => 'csrfcheck',
'type' => 'csrf'
]
);
$this->add(
[
'name' => 'order',
'type' => OrderFieldset::class,
'options' => [
'use_as_base_fieldset' => true
]
]
);
$this->add(
[
'name' => 'order-person',
'type' => OrderPersonFieldset::class,
'options' => [
'use_as_base_fieldset' => true
]
]
);
$this->add(
[
'name' => 'person-address',
'type' => PersonAddressFieldset::class,
'options' => [
'use_as_base_fieldset' => true
]
]
);
$this->add(
[
'name' => 'submit',
'type' => 'submit',
'attributes' => [
'value' => 'Update',
'class' => 'form-element'
]
]
);
$this->getInputFilter()->add($this->orderFilter, 'order');
$this->getInputFilter()->add($this->orderPersonFilter, 'order-person');
$this->getInputFilter()->add($this->personAddressFilter, 'person-address');
}
}
The Checkout Form has a factory:
<?php
namespace MyCart\Form\Factory;
use MyCart\Form\CheckoutForm;
use MyCart\InputFilter\OrderFilter;
use MyCart\InputFilter\OrderPersonFilter;
use MyCart\InputFilter\PersonAddressFilter;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CheckoutFormFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$inputFilter = $serviceLocator->getServiceLocator()->get('InputFilterManager');
return new CheckoutForm(
$inputFilter->get(OrderFilter::class),
$inputFilter->get(OrderPersonFilter::class),
$inputFilter->get(PersonAddressFilter::class)
);
}
}
AS you can see the Checkout Form references the three input filters that are all constructed in the same manner. I have the OrderPerson fieldset here as a reference:
<?php
namespace MyCart\Form;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use MyCart\Entity\OrderPerson;
use Zend\Form\Element;
use Zend\Form\Fieldset;
class OrderPersonFieldset extends Fieldset
{
/**
* #var \Doctrine\Common\Persistence\ObjectManager
* #access protected
*/
protected $objectManager;
/**
* #param ObjectManager $objectManager
* #param OrderPerson $orderPrototype
* #param null $name
* #param array $options
*/
public function __construct(
ObjectManager $objectManager,
OrderPerson $orderPrototype,
$name = null,
$options = array()
) {
parent::__construct($name, $options);
$this->objectManager = $objectManager;
$this->setHydrator(new DoctrineObject($objectManager));
$this->setObject($orderPrototype);
}
public function init()
{
$this->add(
[
'name' => 'id',
'type' => 'hidden',
]
);
$this->add(
[
'name' => 'rbu_user_id',
'type' => 'hidden',
]
);
$this->add(
[
'type' => 'text',
'name' => 'email',
'attributes' => [
'class' => 'form-control',
'required' => 'required',
],
'options' => [
'label' => 'Email Address',
'instructions' => 'Please enter your email address',
],
]
);
//etc...
}
}
On execution of the form the CheckoutController is called and this is where I am unsure of what to do or how to save the various objects, this is what I have so far:
public function indexAction()
{
//Pre check stuff here
//Start the form processing
$prg = $this->prg();
if ($prg instanceof Response) {
return $prg;
} elseif ($prg === false) {
//If logged in
if ( $this->authService->hasIdentity()) {
//Update the OrderPerson entity
$user_id = $this->identity()->getId();
$userObject = $this->userService->find($user_id);
$orderPersonEntity = new OrderPerson();
$orderPersonEntity->setRbuUser($userObject);
$orderPersonEntity->setFirstname($userObject->getFirstname());
$orderPersonEntity->setSurname($userObject->getSurname());
$this->checkoutForm->bind($orderPersonEntity);
//Update the person address entity
$personAddress = new PersonAddress();
$personAddress->setAddress1($userObject->getAddress1());
$personAddress->setAddress2($userObject->getAddress2());
$this->checkoutForm->bind($personAddress);
//Update the order entity
}
return new ViewModel(
array(
'cart' => $cart,
'form' => $this->checkoutForm
)
);
}
$this->checkoutForm->setData($prg);
if (!$this->checkoutForm->isValid()) {
return new ViewModel(
array(
'cart' => $cart,
'form' => $this->checkoutForm
)
);
}
$checkoutObject = $this->checkoutForm->getData();
die(var_dump($checkoutObject));
Dumping the result here outputs only the person address object:
object(MyCart\Entity\PersonAddress)[1177]
private 'id' => null
private 'address1' => string 'xx' (length=2)
private 'address2' => string 'xx' (length=2)
private 'region' => null
private 'postCode' => string 'xx' (length=2)
private 'city' => string 'xx' (length=2)
private 'country' => string 'xx' (length=2)
private 'created' => null
private 'modified' => null
private 'orderPerson' => null
So my question is simply, is this the right thinking and how do I complete this so that I can save the three entities in a single go?
I want to know how I can modify the error message on my ContactType.
It's possible directly in the Type ?
My current code:
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$collectionConstraint = new Collection(array(
'name' => array(
new NotBlank(array('message' => 'My custon message.')),
new Length(array('min' => 2), array('message' => 'My custon message.'))
),
'email' => array(
new NotBlank(array('message' => 'My custon message.')),
new Email(array('message' => 'My custon message.'))
),
'subject' => array(
new NotBlank(array('message' => 'My custon message.')),
new Length(array('min' => 10), array('message' => 'My custon message.'))
),
'message' => array(
new NotBlank(array('message' => 'My custon message')),
new Length(array('min' => 5))
)
));
$resolver->setDefaults(array(
'constraints' => $collectionConstraint
));
}
public function getName()
{
return 'contact';
}
}
Thanks you all for your helping.
Best regards,
It's recommend to change the message of the assertion instead, but you can also use the invalid_message setting of a form type.
I have created a ZF2 project with the following dependencies:
"php": ">=5.3.3",
"zendframework/zendframework": "2.*",
"zendframework/zend-developer-tools": "dev-master",
"doctrine/doctrine-orm-module": "dev-master",
"bjyoungblood/bjy-profiler": "dev-master"
I created a User Entity and a RegisterUser Form. In my index action I bind the User Entity to my register form and pass it to my view - like so:
public function indexAction() {
$entityManager = $this->getServiceLocator ()->get ( 'Doctrine\ORM\EntityManager' );
$form = new RegisterUserForm ( $this->serviceLocator );
$user = new User ();
$form->bind ( $user );
if ($this->getRequest()->isPost()) {
$form->setData ( $this->getRequest()->getPost () );
if ($form->isValid ()) {
$entityManager->persist($user);
$entityManager->flush();
}
}
return new ViewModel(array('form' => $form));
}
Now I would like to add another form (login) to the same view. I am having some trouble figuring out the best way to do so. Binding multiple forms to the same User entity feels dangerous, and I'm not sure how to handle the posts in a different actions.
Here is a look at my form:
class RegisterUserForm extends Form
{
public function __construct(ServiceManager $serviceManager)
{
parent::__construct('register-user-form');
$entityManager = $serviceManager->get('Doctrine\ORM\EntityManager');
$this->setHydrator(new DoctrineHydrator ($entityManager, 'Application\Entity\User'));
$userPostFieldset = new UserPostFieldset($serviceManager);
$userPostFieldset->setUseAsBaseFieldset(true);
$this->add($userPostFieldset);
}
}
And the corresponding Fieldset:
class UserPostFieldset extends Fieldset implements InputFilterProviderInterface {
public function __construct(ServiceManager $serviceManager) {
parent::__construct ( 'user' );
$entityManager = $serviceManager->get ( 'Doctrine\ORM\EntityManager' );
$this->setHydrator ( new DoctrineHydrator ( $entityManager, 'Application\Entity\User' ) )->setObject ( new User () );
$this->add ( array (
'type' => 'Zend\Form\Element\Text',
'name' => 'firstName',
'options' => array (
'label' => 'First Name'
)
) );
$this->add ( array (
'type' => 'Zend\Form\Element\Email',
'name' => 'email',
'options' => array (
'label' => 'Email Address'
)
) );
$this->add ( array (
'type' => 'Zend\Form\Element\Email',
'name' => 'emailCheck',
'options' => array (
'label' => 'Re-Enter Email Address'
)
) );
$this->add ( array (
'name' => 'submit',
'attributes' => array (
'type' => 'submit',
'value' => 'Submit'
)
) );
}
public function getInputFilterSpecification() {
return array (
'email' => array (
'required' => true,
'filters' => array (
array (
'name' => 'StripTags'
),
array (
'name' => 'StringTrim'
)
),
'validators' => array (
array (
'name' => 'Identical',
'options' => array (
'token' => 'emailCheck',
'message' => 'Emails do not match'
)
)
)
)
);
}
}
Any insight would be appreciated.
Questions:
1 - What is the best practice for adding multiple forms to a view that deal with the same Entity?
2 - How can I break apart my index action so it passes multiple forms to my view, and then map the posts to different actions to perform validation checks?
Thanks,
D
Are those Forms really identical? A register Form usually has at least either a PW-Confirmation Field, OR a PW-Checkbox to display the PW as a text.
Ultimately though, you'd simply pass 2 Form-Objects to your View. Both Form-Objects should point to a different action like:
// Class Module#getServiceConfig()
return array( 'factories' => array(
'my-login-form' => function($sm) {
$url = $sm->get('viewhelpermanager')->get('url');
$form = new RegisterLoginForm($sm);
$form->setAttribute('action', $url('my/route/login'));
return $form;
}
'my-register-form' => function($sm) {
$url = $sm->get('viewhelpermanager')->get('url');
$form = new RegisterLoginForm($sm);
$form->setAttribute('action', $url('my/route/register'));
return $form;
}
This way you have two Form Objects and everything should work out just well. Your Controller then would simply look like this:
$sl = $this->getServiceLocator();
$formLogin = $sl->get('my-login-form');
$formRegister = $sl->get('my-register-form');
return array(
'loginform' => $formLogin,
'registerform' => $formRegister
);
Realize, that the final "bind" to your Entities will only be made upon posting the form. Therefore only one form / entity is concerned.
I want to set EntitiesManager instance available on my Controller. I want the Instance available with $this->em on Controller. This is my configuration :
module.config.php
[...]
'doctrine' => array(
'driver' => array(
'application_entities' => array(
'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Application/Entity')
),
'orm_default' => array(
'drivers' => array(
'Application\Entity' => 'application_entities'
),
),
),
),
'di' => array(
'instance' => array(
'alias' => array(
'application' => 'Application\Controller\IndexController',
),
'Application\Controller\IndexController' => array(
'parameters' => array(
'em' => 'doctrine_em',
),
),
),
),
IndexController.php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use \Doctrine\ORM\EntityManager;
class IndexController extends AbstractActionController
{
protected $em;
public function setEm(EntityManager $em) {
$this->em = $em;
return $this;
}
public function indexAction() {
$user1 = $this->em->find('\Application\Entity\User', 1);
var_dump($user1->getFullname());
}
I got this error : "Fatal error: Call to a member function find() on a non-object". It seem than EntityManager is not correctly instantiate.
What Am I missing?
Thank you!
You dont need anything in the module config
You edit the Module.php, onbootstrap function
public function onBootstrap(\Zend\EventManager\EventInterface $e) {
$serviceManager = $e->getApplication()->getServiceManager();
$em = $serviceManager;
$controllerLoader = $serviceManager->get('ControllerLoader');
$controllerLoader->addInitializer(function ($controller) use ($em) {
if (method_exists($controller, 'setEm')) {
$controller->setEm($em);
}
});
}
the controller stays as you have it, with the $em field, and the setEm method
//..
protected $em;
public function setEm(EntityManager $em) {
$this->em = $em;
return $this;
}
//..