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.
Related
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.
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'm learning how to use Zend Framework2. According to some tutorials available on the Net I've wrote some pieces of code . The most important tutorial for me is this one: https://github.com/psamatt/zf2-doctrine-example It covers most of the basics that i've planned to write. I've stuck on one problem that looks strange to me. On my summary page, that display all the records from DB I have a links to add new record, edit existing record, and delete record. Routing is covered by module.config.php:
'router' => array(
'routes' => array(
'incident' => array(
'type' => 'segment',
'options' => array(
'route' => '/incident[/][:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Helpdesk\Controller\Incident',
'action' => 'index',
),
),
),
),
),
When I use a link to a new record (h.t.t.p://helpdesk/incident/add) everything works correctly. But when I use a link to edit my record (h.t.t.p://helpdesk/incident/edit/1 - where 1 is example record ID) I receive an error:
Zend\View\Renderer\PhpRenderer::render: Unable to render template "helpdesk/incident/edit"; resolver could not resolve to a file
This is my IncidentController.php:
<?php
namespace Helpdesk\Controller;
use Application\Controller\EntityUsingController;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use Doctrine\ORM\EntityManager;
use Zend\View\Model\ViewModel;
use Helpdesk\Form\IncidentForm;
use Helpdesk\Entity\Incident;
class IncidentController extends EntityUsingController
{
/**
* Index action
*
*/
public function indexAction()
{
$em = $this->getEntityManager();
$incidents = $em->getRepository('Helpdesk\Entity\Incident')->findAll();
return new ViewModel(array(
'incidents' => $incidents
));
}
/**
* Edit action
*
*/
public function editAction()
{
$incident = new Incident();
if ($this->params('id') > 0) {
$incident = $this->getEntityManager()->getRepository('Helpdesk\Entity\Incident')->find($this->params('id'));
}
$form = new IncidentForm($this->getEntityManager());
$form->bind($incident);
$form->setHydrator(new DoctrineObject($this->getEntityManager(),'Helpdesk\Entity\Incident'));
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($incident->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$em = $this->getEntityManager();
$em->persist($incident);
$em->flush();
$this->flashMessenger()->addSuccessMessage('Incident saved');
// Redirect to list of incidents
return $this->redirect()->toRoute('incident');
}
}
return array(
'incident' => $incident,
'form' => $form,
);
}
/**
* Add action
*
*/
public function addAction()
{
return $this->editAction();
}
/**
* Delete action
*
*/
public function deleteAction()
{
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('incident');
}
$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->post()->get('del', 'No');
if ($del == 'Yes') {
$id = (int)$request->post()->get('id');
$incident = $this->getEntityManager()->find('Helpdesk\Entity\Incident', $id);
if ($incident) {
$this->getEntityManager()->remove($incident);
$this->getEntityManager()->flush();
}
}
// Redirect to list of incidents
return $this->redirect()->toRoute('default', array(
'controller' => 'incident',
'action' => 'index',
));
}
return array(
'id' => $id,
'incident' => $this->getEntityManager()->find('Helpdesk\Entity\Incident', $id)->getArrayCopy()
);
}
}
What is the difference between these two? Why one works fine, while the second one generates an error?
Thanks for your help
Smok.
Most likely helpdesk/incident/edit.phtml does not exist, while add action is rendering an existing helpdesk/incident/add.phtml.
You can reuse the existing helpdesk/incident/add.phtml or create a new one.
I would like to return an array of string in my web services
I've tryed :
<?php
require_once('nusoap/nusoap.php');
$server = new soap_server();
$server->configureWSDL('NewsService', 'urn:NewsService');
$server->register('GetAllNews',
array(),
array('return' => 'xsd:string[]'),
'urn:NewsService',
'urn:NewsService#GetAllNews',
'rpc',
'literal',
''
);
// Define the method as a PHP function
function GetAllNews()
{
$stack = array("orange", "banana");
array_push($stack, "apple", "raspberry");
return $stack;
}
but it doesn't work.
What is the correct syntax for that ?
Thanks in advance for any help
You can't return an array like this. To return an array, you have to define a complex type.
I'll provide u an example...
The server program service.php:
<?php
// Pull in the NuSOAP code
require_once('lib/nusoap.php');
// Create the server instance
$server = new soap_server();
// Initialize WSDL support
$server->configureWSDL('RM', 'urn:RM');
//Define complex type
$server->wsdl->addComplexType(
'User',
'complexType',
'struct',
'all',
'',
array(
'Id' => array('name' => 'Id', 'type' => 'xsd:int'),
'Name' => array('name' => 'Name', 'type' => 'xsd:string'),
'Email' => array('name' => 'Email', 'type' => 'xsd:string'),
'Description' => array('name' => 'Description', 'type' => 'xsd:string')
)
);
// Register the method
$server->register('GetUser', // method name
array('UserName'=> 'xsd:string'), // input parameters
array('User' => 'tns:User'), // output parameters
'urn:RM', // namespace
'urn:RM#GetUser', // soapaction
'rpc', // style
'encoded', // use
'Get User Details' // documentation
);
function GetUser($UserName) {
return array('Id' => 1,
'Name' => $UserName,
'Email' =>'test#a.com',
'Description' =>'My desc'
);
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>
And the client program client.php:
<?php
// Pull in the NuSOAP code
require_once('lib/nusoap.php');
// Create the client instance
$client = new soapclient('http://localhost/Service/service.php');
// Call the SOAP method
$result = $client->call('GetUser', array('UserName' => 'Jim'));
// Display the result
print_r($result);
?>