I'm trying to create one page with a Form with two fieldsets that should each populate a different table.
I can easily create One form as in the Album tutorial, and bind the data like this:
$pageForm = new PageForm();
$pageForm->bind($page);
with my PageForm class as follows:
class PageForm extends Form
{
public function __construct($name = null)
{
// we want to ignore the name passed
parent::__construct('page');
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));
} /// and a bunch of other elements
but if I put these elements into fieldsets the bind no longer works, besides I would need to bind each fieldset to a separate table, and they need to save into the separate tables once the form is submited.
How would I go about this, I think I can do it using two forms but that is probably not the right way to go about it (If I understand the concept of fieldsets correctly)?
you have to use setObject in each Fieldset and provide a hydrator to it. eg:
<?php
// file My/Form/Product.php
namespace My\Form;
use Zend\Form\Fieldset;
use My\Entity\Product as ProductEntity;
use Zend\Stdlib\Hydrator\ClassMethods();
class Product extends Fieldset
{
public function __construct($name = 'product')
{
parent::__construct($name);
$this->setObject(new ProductEntity())
->setHydrator(new ClassMethods());
$this->add(array(
'name' => 'name',
'options' => array('label' => 'Product name'),
));
// Brand fieldset
$brand = new Brand();
$this->add($brand);
}
}
// File My/Form/Brand.php
namespace My\Form;
use Zend\Form\Fieldset;
use My\Entity\Brand as BrandEntity;
use Zend\Stdlib\Hydrator\ClassMethods();
class Brand extends Fieldset
{
public function __construct($name = 'brand')
{
parent::__construct($name = 'brand');
$this->setObject(new BrandEntity())
->setHydrator(new ClassMethods());
$this->add(array(
'name' => 'name',
'options' => array('label' => 'Brand name'),
));
}
}
// File My/Form/ProductForm.php
namespace My\Form;
use Zend\Form\Form;
use My\Entity\Product as ProductEntity;
use Zend\Stdlib\Hydrator\ClassMethods();
class ProductForm extends Form
{
public function __construct($name = 'product')
{
parent::__construct($name);
$this->setObject(new ProductEntity())
->setHydrator(new ClassMethods());
// Product Fieldset
// Here, we define Product fieldset as base fieldset
$product = new Product();
$product->setUseAsBaseFieldset(true);
$this->add($product);
}
}
// In Module.php
// ...
public function getServiceConfig()
{
return array(
'invokables' => array(
'My\Form\Product' => 'My\Form\Product',
),
);
}
// ...
// In Controller
// You don't need to use $form->bind($productEntity), except if you're editing a product.
// The form already has an Object, do you remenber??? "$this->setObject(new ProductEntity())" on form???
// ...
$form = $this->getServiceLocator()->get('My\Form\Product');
// ...
Related
I have two very similar Fieldsets MyFooFieldset and MyBarFieldset. In order to avoid code duplication, I created an AbstractMyFieldset, moved the whole code there, and want to handle the differences in the init() methods of the concrete classes:
AbstractMyFooFieldset
namespace My\Form\Fieldset;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
abstract class AbstractMyFieldset extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
$this->add(
[
'type' => 'multi_checkbox',
'name' => 'my_field',
'options' => [
'label_attributes' => [
'class' => '...'
],
'value_options' => $this->getValueOptions()
]
]);
}
public function getInputFilterSpecification()
{
return [...];
}
protected function getValueOptions()
{
...
return $valueOptions;
}
}
MyFooServerFieldset
namespace My\Form\Fieldset;
use Zend\Form\Fieldset;
class MyFooServerFieldset extends AbstractMyFieldset
{
public function init()
{
parent::init();
$this->get('my_field')->setType('radio'); // There is not method Element#setType(...)! How to do this?
$this->get('my_field')->setAttribute('required', 'required'); // But this works.
}
}
I want to set the type and some other configurations for the element, e.g. the type and the required attribute. Setting attributes seems to be OK, at least I can set the required attribute. But I cannot set the type -- the Element#setType(...) is not there.
How to set the type of a Zend\Form\Element, after it has been added?
There is no way to set the type of an element as each element has its own type and element class defined. In your AbstractMyFieldset, see the "Type" key within your init(). You tell the form to add the MultiCheckbox element class and want to change the class to another one. So you need to either remove the default and copy it's attributes and options over to a newly added Zend Form element.
Another option is to use the base Zend\Form\Element class you can overwrite the attributes and set the type attribute. ->setAttribute('type', 'my_type') but ur missing all the benefits of the default Zend2 form classes. Especially as the default InArray validator for Zend\Form\Element\Radio or the Zend\Form\Element\MultiCheckbox.
Or you should just consider making an abstractFieldSet for the both fieldsets and define how they get their value options and reuse that. Like:
abstract class AbstractFieldSet extends Fieldset {
public function addMyField($isRadio = false)
{
$this->add([
'type' => $isRadio ? 'radio' : 'multi_checkbox',
'name' => 'my_field',
'options' => [
'value_options' => $this->getValueOptions()
]
]);
}
protected function getValueOptions()
{
// ..
return $valueOptions
}
}
class fieldSet1 extends AbstractFieldSet {
public function init()
{
$this->addMyField(false);
}
}
class fieldSet2 extends AbstractFieldSet {
public function init()
{
$this->addMyField(true);
}
}
UPDATED!
ZF2, l10n view helper. I can't understand how to use my view helper inside of a class.
I want to use it like: $this->t('STRING TO TRANSLATE');
example bellow
NB! i'm only localizing project, i'm not allowed to change code structure or smth like that.Also i'm absolute newb in ZF2.
my Class -
class Project extends InputFilter{
as i understood i have to implement ServiceLocatorAwareInterface interface, tried this:
use Zend\ServiceManager\ServiceLocatorInterface as ServiceLocator;
class Project extends InputFilter implements ServiceLocator
{
protected $services;
public function __construct(Connection $p4, $mode, ServiceLocator $services)
{
$this->services = $services;
//some code
$this->add(...);
$this->add(
array(
'name' => 'name',
'filters' => array('trim'),
'validators' => array(
array(
'name' => 'NotEmpty',
'options' => array(
'message' => "Name is required and can't be empty."
)
),
array(
'name' => '\Application\Validator\Callback',
'options' => array(
'callback' => function ($value) use ($p4, $toId, $mode, $reserved) {
$id = $toId($value);
if (!$id) {
return $this->t('STRING TO TRANSLATE');
}
// more code here
return true;
}
)
)
)
)
);
//some code
$this->add(...);
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator() {
return $this->serviceLocator;
}
//how to get this method work ???
public function t($msg) {
$translate = $this->services->get('ViewHelperManager')->get('t');
return $translate($msg);
}
}
Usage in Controller:
use Projects\Filter\Project as ProjectFilter;
...
protected function doAddEdit($mode, $project = null)
{
$p4Admin = $this->getServiceLocator()->get('p4_admin');
$request = $this->getRequest();
// process add request.
if ($request->isPost()) {
// pull out the data
$data = $request->getPost();
// configure our filter with the p4 connection and add/edit mode
$filter = new ProjectFilter($p4Admin, $mode); //
$filter->setData($data);
// if the data is valid, setup the project and save it
$isValid = $filter->isValid();
if ($isValid) {
$values = $filter->getValues();
$project = new Project($p4Admin);
$project->set($values)
->save();
}
return new JsonModel(
array(
'isValid' => $isValid,
'messages' => $filter->getMessages(), // THESE array of messages i want to localize
'redirect' => '/projects/' . $filter->getValue('id')
)
);
}
// prepare view for form.
$view = new ViewModel;
$view->setVariables(
array(
'mode' => $mode,
'project' => $project ?: new Project
)
);
return $view;
}
What am i doing wrong ?
You're calling tr method inside a class constructor. tr method calls $this->getServiceLocator(). $this->getServiceLocator() will not return a service locator instance since it'll only be injected by service manager after it has created the object that implements ServiceLocatorAwareInterface.
So you'd have to pass a service locator instance to the constructor, or don't depend on it in your __construct method. Probably the easiest way to go is to move your code from the constructor to the init method. Init is supposed to get called automatically as long as you get your input filter through the InputFilterManager.
Also I don't think you need the translator view helper. You should be able to get it from the service manager like so: $serviceManager->get('translator')
There is no need to do this at all the validation message will be translated by the validator anyway. But your config is a bit wrong I think
$this->add(
array(
'name' => 'name',
'filters' => array('trim'),)
'validators' => array(
array(
'name' => 'NotEmpty',
'options' => array(
'messages' => array(
\Zend\Validator\NotEmpty::IS_EMPTY => 'YOUR_TRANSLATION_STRING_IS_EMPTY',
\Zend\Validator\NotEmpty::INVALID => 'YOUR_TRANSLATION_STRING_INVALID',
)
)
)
),
Have a read of https://zf2-docs.readthedocs.org/en/latest/modules/zend.validator.html#translating-messages
You will need to ensure you are managing your dependencies correctly for this to work so really depends how you are using the input filter.
If your not directly adding to a form or using InputFilterAwareInterface on your model you'll need to make sure your InputFilter is registered with InputFilterPluginManager and you retrieve it using InputFilterPluginManager rather than instantiating directly
I want create a custom element and use the short name for add the element into Form, using the new ServiceManager tecnique for ZF2 V.2.1+
I am try to copy the same sample of the zend documentation step to step but it not works.
When I use the service writting the short name, it raises a exception because service not found:
Zend\ServiceManager\Exception\ServiceNotFoundException
File:
Zend\ServiceManager\ServiceManager.php:456
Message:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Test
I think I have all classes identically, see follows
This is my custom element:
namespace SecureDraw\Form\Element;
use Zend\Form\Element\Text;
class ProvaElement extends Text {
protected $hola;
public function hola(){
return 'hola';
}
}
This is my Module.php I have my invokable service be able to use short name:
class Module implements FormElementProviderInterface {
//Rest of class
public function getFormElementConfig() {
return array(
'invokables' => array(
'Test' => 'SecureDraw\Form\Element\ProvaElement'
)
);
}
}
In my form I use for add the element, the commented line works ok, but with short name not works:
$this->add(array(
'name' => 'prova',
//'type' => 'SecureDraw\Form\Element\ProvaElement',
'type' => 'Test', //Fail
));
In my action:
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('SecureDraw\Form\UserForm');
$prova = $form->get('prova');
echo $prova->hola();
The problem is that the elements created via FormElementManager have to be created into init method instead __Construct method how it can see in this page.
The zend documentation is badly explained
Workaround:
In your own module, create the following two files:
FormElementManagerConfig with the invokables short names of your custom form elements;
Subclass Form\Factory and override getFormElementManager and pass the config to the FormElementManager constructor;
You then use your own Factory to create your Form, like this (you can pass a very rudimentary, e.g. empty array, or a more or less full-fledged $spec to $factory->createForm()):
$factory = new Factory();
$spec = array();
$form = $factory->createForm($spec);
FormElementManagerConfig.php:
class FormElementManagerConfig implements ConfigInterface
{
protected $invokables = array(
'anything' => 'MyModule\Form\Element\Anything',
);
public function configureServiceManager(ServiceManager $serviceManager)
{
foreach ($this->invokables as $name => $service) {
$serviceManager->setInvokableClass($name, $service);
}
}
}
MyFactory.php:
class Factory extends \Zend\Form\Factory
{
public function getFormElementManager()
{
if ($this->formElementManager === null) {
$this->setFormElementManager(new FormElementManager(new FormElementManagerConfig()));
}
return $this->formElementManager;
}
}
I call the Forward plugin from one Controller's action method to get the value from the other Controller's action method:
namespace Foo/Controller;
class FooController {
public function indexAction() {
// I expect the $result to be an associative array,
// but the $result is an instance of the Zend\View\Model\ViewModel
$result = $this->forward()->dispatch('Boo/Controller/Boo',
array(
'action' => 'start'
));
}
}
And here's Boo Controller I apply to:
namespace Boo/Controller;
class BooController {
public function startAction() {
// I want this array to be returned,
// but an instance of the ViewModel is returned instead
return array(
'one' => 'value one',
'two' => 'value two',
'three' => 'value three',
);
}
}
And if I print_r($result) it is the ViewModel of the error/404 page:
Zend\View\Model\ViewModel Object
(
[captureTo:protected] => content
[children:protected] => Array
(
)
[options:protected] => Array
(
)
[template:protected] => error/404
[terminate:protected] =>
[variables:protected] => Array
(
[content] => Page not found
[message] => Page not found.
[reason] => error-controller-cannot-dispatch
)
[append:protected] =>
)
What is going on? How to change this behavior and get the required data type from the Forward plugin?
UPD 1
For now found only this here:
The MVC registers a couple of listeners for controllers to automate
this. The first will look to see if you returned an associative array
from your controller; if so, it will create a View Model and make this
associative array the Variables Container; this View Model then
replaces the MvcEvent‘s result.
And this doesn't work:
$this->getEvent()->setResult(array(
'one' => 'value one',
'two' => 'value two',
'three' => 'value three',
));
return $this->getEvent()->getResult(); // doesn't work, returns ViewModel anyway
It means instead of to get just an array I have to put variable into a ViewModel, return a ViewModel and get those variable from the ViewModel. Very good design, I can say.
You have to disable view in your action in ZF2. You can do this in this way:
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
public function indexAction()
{
$result = $this->forward()->dispatch('Application/Controller/Index', array( 'action' => 'foo' ));
print_r($result->getContent());
exit;
}
public function fooAction()
{
$response = $this->getResponse();
$response->setStatusCode(200);
$response->setContent(array('foo' => 'bar'));
return $response;
}
}
I need to get the adapter from the form, but still could not.
In my controller I can recover the adapter using the following:
// module/Users/src/Users/Controller/UsersController.php
public function getUsersTable ()
{
if (! $this->usersTable) {
$sm = $this->getServiceLocator();
$this->usersTable = $sm->get('Users\Model\UsersTable');
}
return $this->usersTable;
}
In my module I did so:
// module/Users/Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'Users\Model\UsersTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$uTable = new UsersTable($dbAdapter);
return $uTable;
},
//I need to get this to the list of groups
'Users\Model\GroupsTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$gTable = new GroupsTable($dbAdapter);
return $gTable;
},
),
);
}
Could someone give me an example how to get the adapter to the table from the group form?
I have followed this example to my form users:
http://framework.zend.com/manual/2.0/en/modules/zend.form.collections.html
EDITED from here...
Maybe I expressed myself wrong to ask the question.
What I really need to do is populate a select (Drop Down) with information from my table groups.
So I need to get the services inside my userForm class by ServiceLocatorAwareInterface (see this link) implemented because By default, the Zend Framework MVC registers an initializer That will inject into the ServiceManager instance ServiceLocatorAwareInterface Implementing any class.
After retrieving the values from the table groups and populate the select.
The problem is that of all the ways that I've tried, the getServiceLocator() returns this:
Call to a member function get() on a non-object in
D:\WEBSERVER\htdocs\Zend2Control\module\Users\src\Users\Form\UsersForm.php
on line 46
I just wanted to do this in my UserForm...
namespace Users\Form;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Form\Element;
use Zend\Form\Form;
class UsersForm extends Form implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
public function getServiceLocator ()
{
return $this->serviceLocator;
}
public function setServiceLocator (ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function __construct ($name = null)
{
parent::__construct('users');
$this->setAttribute('method', 'post');
$sm = $this->getServiceLocator();
$groups = $sm->get('Users\Model\GroupsTable')->fetchAll(); // line 46
$select = new Element\Select('groups');
$options = array();
foreach ($groups as $group) {
$options[$group->id] = $group->name;
}
$select->setValueOptions($options);
$this->add($select);
// and more elements here...
The other various answers here generally correct, for ZF < 2.1.
Once 2.1 is out, the framework has a pretty nice solution. This more or less formalizes DrBeza's solution, ie: using an initializer, and then moving any form-bootstrapping into an init() method that is called after all dependencies have been initialized.
I've been playing with the development branch, it it works quite well.
This is the method I used to get around that issue.
firstly, you want to make your form implement ServiceLocatorInterface as you have done.
You will then still need to manually inject the service locator, and as the whole form is generated inside the contrstuctor you will need to inject via the contructor too (no ideal to build it all in the constructor though)
Module.php
/**
* Get the service Config
*
* #return array
*/
public function getServiceConfig()
{
return array(
'factories' => array(
/**
* Inject ServiceLocator into our Form
*/
'MyModule\Form\MyForm' => function($sm) {
$form = new \MyModule\Form\MyFormForm('formname', $sm);
//$form->setServiceLocator($sm);
// Alternativly you can inject the adapter/gateway directly
// just add a setter on your form object...
//$form->setAdapter($sm->get('Users\Model\GroupsTable'));
return $form;
},
),
);
}
Now inside your controller you get your form like this:
// Service locator now injected
$form = $this->getServiceLocator()->get('MyModule\Form\MyForm');
Now you will have access to the full service locator inside the form, to get hold of any other services etc such as:
$groups = $this->getServiceLocator()->get('Users\Model\GroupsTable')->fetchAll();
In module.php I create two services. See how I feed the adapter to the form.
public function getServiceConfig()
{
return array(
'factories' => array(
'db_adapter' => function($sm) {
$config = $sm->get('Configuration');
$dbAdapter = new \Zend\Db\Adapter\Adapter($config['db']);
return $dbAdapter;
},
'my_amazing_form' => function ($sm) {
return new \dir\Form\SomeForm($sm->get('db_adapter'));
},
),
);
}
In the form code I use that feed to whatever:
namespace ....\Form;
use Zend\Form\Factory as FormFactory;
use Zend\Form\Form;
class SomeForm extends Form
{
public function __construct($adapter, $name = null)
{
parent::__construct($name);
$factory = new FormFactory();
if (null === $name) {
$this->setName('whatever');
}
}
}
We handle this in the model, by adding a method that accepts a form
public function buildFormSelectOptions($form, $context = null)
{
/**
* Do this this for each form element that needs options added
*/
$model = $this->getServiceManager()->get('modelProject');
if (empty($context)){
$optionRecords = $model->findAll();
} else {
/**
* other logic for $optionRecords
*/
}
$options = array('value'=>'', 'label'=>'Choose a Project');
foreach ($optionRecords as $option) {
$options[] = array('value'=>$option->getId(), 'label'=>$option->getName());
}
$form->get('project')->setAttribute('options', $options);
}
As the form is passed by reference, we can do something like this in the controller where the form is built:
$builder = new AnnotationBuilder();
$form = $builder->createForm($myEntity);
$myModel->buildFormSelectOptions($form, $myEntity);
$form->add(array(
'name' => 'submitbutton',
'attributes' => array(
'type' => 'submit',
'value' => 'Submit',
'id' => 'submitbutton',
),
));
$form->add(array(
'name' => 'cancel',
'attributes' => array(
'type' => 'submit',
'value' => 'Cancel',
'id' => 'cancel',
),
));
Note: The example assumes the base form is build via annotations, but it doesn't matter how you create the initial form.
An alternative method to the other answers would be to create a ServiceManager Initializer.
An example of an existing Initializer is how the ServiceManager is injected if your instance implements ServiceLocatorAwareInterface.
The idea would be to create an interface that you check for in your Initialiser, this interface may look like:
interface FormServiceAwareInterface
{
public function init();
public function setServiceManager(ServiceManager $serviceManager);
}
An example of what your Initializer may look like:
class FormInitializer implements InitializerInterface
{
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if (!$instance instanceof FormServiceAwareInterface)
{
return;
}
$instance->setServiceManager($serviceLocator);
$instance->init();
}
}
Anything that happens in init() would have access to the ServiceManager. Of course you would need to add your initializer to your SM configuration.
It is not perfect but it works fine for my needs and can also be applied to any Fieldsets pulled from the ServiceManager.
This is the way I used get around that issue.
firstly, In Module.php, create the service (just as you have done):
// module/Users/Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'Users\Model\UsersTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$uTable = new UsersTable($dbAdapter);
return $uTable;
},
//I need to get this to the list of groups
'Users\Model\GroupsTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$gTable = new GroupsTable($dbAdapter);
return $gTable;
},
),
);
}
Then in the controller, I got a reference to the Service:
$users = $this->getServiceLocator()->get('Test\Model\TestGroupTable')->fetchAll();
$options = array();
foreach ($users as $user)
$options[$user->id] = $user->name;
//get the form element
$form->get('user_id')->setValueOptions($options);
And viola, that work.