How to set the type of an element in a Fieldset child class in Zend Framework 2? - zend-framework2

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);
}
}

Related

ZF2 nested data validation

I'm trying make to work my validation. I have data posted to controller in the format like this:
[
'property' => 'value',
'nested_property' => [
'property' => 'value',
// ...
]
]
I have divided fields/filters and form into different classes and just gather it together in the Form's controller that looks like that:
public function __construct($name, $options)
{
// ...
$this->add(new SomeFieldset($name, $options));
$this->setInputFilter(new SomeInputFilter());
}
But it doesn't work properly, looks like it just ignores nested array (or ignores everything). What have I missed?
Thank you.
You need to set up your inputfilter like the way you've setup your forms including the fieldsets if you use the InputFilter class.
So when you've got a structure like:
MyForm
1.1 NestedFieldset
1.2 AnotherFieldset
Your inputfilters need to have the same structure:
MyFormInputFilter
1.1 NestedFielsetInputFilter
1.2 AnotherFieldsetInputFilter
Some example code:
class ExampleForm extends Form
{
public function __construct($name, $options)
{
// handle the dependencies
parent::__construct($name, $options);
$this->setInputFilter(new ExampleInputFilter());
}
public function init()
{
// some fields within your form
$this->add(new SomeFieldset('SomeFieldset'));
}
}
class SomeFieldset extends Fieldset
{
public function __construct($name = null, array $options = [])
{
parent::__construct($name, $options);
}
public function init()
{
// some fields
}
}
class ExampleInputFilter extends InputFilter
{
public function __construct()
{
// configure your validation for your form
$this->add(new SomeFieldsetInputFilter(), 'SomeFieldset');
}
}
class SomeFieldsetInputFilter extends InputFilter
{
public function __construct()
{
// configure your validation for your SomeFieldset
}
}
So the important part of configuring your inputFilter for these situations is that you need to reuse the name of your fieldset when using: $this->add($input, $name = null) within your InputFilter classes.

how to inject class dependency in Yii2 configuration?

I am learning Yii2. Here is one situation I have not googled the answer.
I register a component called scraper in config/console.php's $config['components'] array,
this scraper class has a public property $_client which is a Goutte\Client class.
I tried to use the following way to set up scraper component, but it is not working, Yii2 did not instantiate $_client as a Goutte\Client object.
$config = [
'scraper' => [
'class' => 'app\models\Scraper',
'_pageSize' => 10,
'_client' => [ //not working. can not instantiate this property as an object
'class' => 'Goutte\Client'
],
],
//...
]
Question: What would be working way to inject the dependency in the configuration?
Yii2 will not instantiate objects beyond the first level in your config array. In other words, scraper will get instantiated as an object, but it's property _client will be instantiated as an array ['class' => 'Goutte\Client'].
You should implement this logic yourself:
class Service extends Component
{
private $_client = null;
public $clientClass;
public function getClient()
{
if (null !== $this->_client) {
return $this->_client;
}
$this->_client = new $clientClass;
return $this->_client;
}
}
Alternatively, you can register Goutte\Client as a separate component, then Yii will properly instantiate it.
UPDATE:
To clarify, instantiating objects from config is done with yii\base\Configurable interface which is implemented in yii\base\Object class. Eventually, this implementation executes Yii::configure:
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
As you see, all properties will be assigned their respective values, so _client will become an array, not an object.
Found another approach in the guide itself: The property targets of the class yii\log\Dispatcher can be initialized with a class names or an objects. To make it working as one expects the init method is overwritten:
/**
* {#inheritdoc}
*/
public function init()
{
parent::init();
foreach ($this->targets as $name => $target) {
if (!$target instanceof Target) {
$this->targets[$name] = Yii::createObject($target);
}
}
}
This allows configuration/initialization of the log component like this:
'log' => [
'class' => 'yii\log\Dispatcher',
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
Note: targets is an array here. But it can be done with a single class/object as well.
So in your case this should be a solution:
namespace app\models;
class Scraper extends ActiveRecord // or extends from anything that actually implements yii\base\Configurable
{
public $_client;
/**
* {#inheritdoc}
*/
public function init()
{
parent::init();
if (!$this->_client instanceof Goutte\Client) {
$this->_client = Yii::createObject($this->_client);
}
}
}
btw: usually underscore prefix in variable names is used for private properties.

ZF2 Fieldsets and Form Binding

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');
// ...

Zend Framework 2 Custom elements using ServiceManager not work

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;
}
}

How to create custom form element in Zend Framework 2?

How can I in ZF2 create custom form element with custom validator? I want to create custom category picker that uses jQuery and content of this element should be rendered from phtml script. In ZF1 it was quite easy but in ZF2 I don't know from where to start.
A form element must implement a Zend\Form\ElementInterface. A default class is the Zend\Form\Element which you can use as a base form:
<?php
namespace MyModule\Form\Element;
use Zend\Form\Element;
class Foo extends Element
{
}
CUSTOM VALIDATOR
You can let the element directly assign a custom validator. Then you must implement the Zend\InputFilter\InputProviderInterface:
<?php
namespace MyModule\Form\Element;
use Zend\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use MyModule\InputFilter\Bar as BarValidator;
class Foo extends Element implements InputProviderInterface
{
protected $validator;
public function getValidator()
{
if (null === $this->validator) {
$this->validator = new BarValidator;
}
return $this->validator;
}
public function getInputSpecification()
{
return array(
'name' => $this->getName(),
'required' => true,
'validators' => array(
$this->getValidator(),
),
);
}
}
CUSTOM RENDERING
At this moment it is a bit complex how Zend Framework handles the rendering of custom form element types. Usually, it just returns plain <input type="text"> elements.
There is one option, then you have to override the Zend\Form\View\Helper\FormElement helper. It is registered as formelement and you must override this view helper in your custom module:
namespace MyModule;
class Module
{
public function getViewHelperConfig()
{
return array(
'invokables' => array(
'formelement' => 'MyModule\Form\View\Helper\FormElement',
'formfoo' => 'MyModule\Form\View\Helper\FormFoo',
),
);
}
}
Furthermore, every form element in Zend Framework 2 is rendered by a view helper. So you create a view helper for your own element, which will render the element's content.
Then you have to create your own form element helper (MyModule\Form\View\Helper\FormElement):
namespace MyModule\Form\View\Helper;
use MyModule\Form\Element;
use Zend\Form\View\Helper\FormElement as BaseFormElement;
use Zend\Form\ElementInterface;
class FormElement extends BaseFormElement
{
public function render(ElementInterface $element)
{
$renderer = $this->getView();
if (!method_exists($renderer, 'plugin')) {
// Bail early if renderer is not pluggable
return '';
}
if ($element instanceof Element\Foo) {
$helper = $renderer->plugin('form_foo');
return $helper($element);
}
return parent::render($element);
}
}
As a last step, create your view helper to render this specific form element:
namespace MyModule\Form\View\Helper;
use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\AbstractHelper;
class Foo extends AbstractHelper
{
public function __invoke(ElementInterface $element)
{
// Render your element here
}
}
If you want to render a .phtml file for example for this form element, load it inside this helper:
namespace MyModule\Form\View\Helper;
use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\AbstractHelper;
class Foo extends AbstractHelper
{
protected $script = 'my-module/form-element/foo';
public function render(ElementInterface $element)
{
return $this->getView()->render($this->script, array(
'element' => $element
));
}
}
It will render a my-module/form-element/foo.phtml and in this script you will have a variable $element which contains your specific form element.

Resources