Usando FieldSets em Forms
Existem algumas diferenças entre trabalhar simplesmente com forms e compor um form com fieldsets. Quando usamos fieldSet para conter os campos e ligamos ele ao formulários ao invés de ter tudo diretamente dentro do formulário, estamos definindo melhor o utilidade de cada um.
Além disso, o fieldSet estará ligada a uma entidade, nos permitindo assim trabalhar com entidade e uma melhor POO, e não simplesmente vetores com dados. Com o decorrer dos exemplos você verá algumas das grandes vantagens de se ter fieldSets
Criando um FieldSet
Um fieldSet é um container de campos de um formulário e que pode ou não estar ligado a uma entidade. Caso isso aconteça, cada campo contido no fieldSet poderá relacionado a um atributo da entidade, como por exemplo, o input com nome “name” do nosso fieldSet poderá ter um representante na entidade, que será um atributo $name.
Algo que deve ser ter em mente é que o Form é um FieldSet, ou seja, Form herda de FieldSet e por isso podemos inserir elementos diretamente nele. E também devemos entender que todo formulário tem um FieldSet base, mas poderá ter outros, apesar de não conseguir imaginar um caso para isso.
namespace Application\Form\Fieldset;
use Zend\Form\Fieldset;
class MyFieldSet extends Fieldset
{
function __construct() {
parent::__construct(‘my-fieldset’);
$this->add(
array(
‘name’ => ‘sex’,
‘type’ => ‘Zend\Form\Element\Select’,
‘options’ => array(
‘label’ => ‘Sex:’,
“value_options” => array(
“” => “- Choose your sex -“,
“0” => “Male”,
“1” => “Female”,
),
),
‘attributes’ => array(
‘style’ => ‘width: 350px’
)
)
);
$this->add(
array(
‘name’ => ‘name’,
‘options’ => array(
‘label’ => ‘Name:’
),
‘attributes’ => array(
‘class’ => ‘biggest’,
)
)
);
}
}
|
Repare que precisamos passar ao construtor da classe pai um nome para nosso FieldSet, que será usado para referenciar os seus campos no ValidationGroup do nosso formulário.
Vínculo com a Entidade
Conceitualmente, podemos dizer que a entidade é a representação de uma tabela do banco de dados em código PHP, por exemplo. Podemos também dizer que o FieldSet é a representação de uma entidade em campos para um formulário. E por fim, um formulário engloba o FieldSet que desejar.
E como relacionar nossa entidade ao nosso FieldSet? Simplesmente chamado o método setObject(Object) e passando como parâmetro uma instância da nossa entidade. Vamos supor que nossa entidade, posteriormente implementada, se chame MyEntity.
namespace Application\Form\Fieldset;
use Application\Entity;
use Zend\Form\Fieldset;
class MyFieldset extends Fieldset
{
function __construct() {
parent::__construct(‘my-fieldset’);
$this->setObject(new Entity\MyEntity);
$this->add(
array(
‘name’ => ‘sex’,
‘type’ => ‘Zend\Form\Element\Select’,
‘options’ => array(
‘label’ => ‘Sex:’,
“value_options” => array(
“” => “- Choose your sex -“,
“0” => “Male”,
“1” => “Female”,
),
),
‘attributes’ => array(
‘style’ => ‘width: 350px’
)
)
);
$this->add(
array(
‘name’ => ‘name’,
‘options’ => array(
‘label’ => ‘Name:’
),
‘attributes’ => array(
‘class’ => ‘biggest’,
)
)
);
}
}
|
A Entidade
Se você já estava usando uma entidade vinculada ao seu form, a única mudança será que ela passará a se vincular ao fieldSet e não mais ao form, como já visto acima. Agora se você não vinculou uma entidade diretamente em seu form, precisamos ter algumas mudanças esclarecidas.
A tal mudança será com a nossa classe entidade que passa a implementar a interface Zend\InputFilter\InputFilterAwareInterface e possuir um atributo que referenciará ao InputFilter, diferente do formulário que herda de Form, pois a classe pai já implementa e utiliza esse detalhes.
Como a entidade representa uma tabela do banco de dados, então é interessante que as restrições de cada coluna estejam representadas nela. Sendo assim, a nossa entidade de exemplo fica dessa maneira.
namespace Application\Entity;
use Zend\InputFilter\InputFilterAwareInterface;
public class MyEntity implements InputFilterAwareInterface{
private $name;
private $sex;
private $inputFilter;
//Getters and Setters..
/**
* Retrieve input filter
*
* @return InputFilterInterface
*/
public function getInputFilter()
{
$myValidator = new Validator\MyValidator();
if (null === $this->inputFilter) {
$this->inputFilter = new Factory()->createInputFilter(
array(
‘sex’ => array (
‘required’ => true,
‘validators’ => array(
$myValidator
)
),
‘name’ => array (
‘allow_empty’ => false,
‘validators’ => array(
array(‘name’ => ‘Application\Validator\Form\MyValidator’)
)
),
)
);
}
return $this->inputFilter;
}
}
|
Form
Com essas mudanças o form deixa de conter todos os campos, criamos um referencia ao nosso fieldSet, e o ValidationGroup da classe Form deixa de referenciar os campos a serem validados de modo direto, mas tendo o vetor do fieldSet indiretamente.
Lembrando que o botão de submissão ainda faz parte do contexto do formulário e não do fieldset, pois esse botão não é uma coluna do banco de dados e nem um atributo da entidade, e por isso ele permanecerá sendo adicionado pelo formulário. Abaixo segue como ficou o form.
namespace Application\Form;
use Zend\Form;
class MyForm extends Form {
function __contruct() {
$this->setAttribute(‘method’, ‘post’);
$this->setAttribute(‘id’, ‘my-form’);
$this->add(
array(
‘type’ => ‘Application\Form\Fieldset\MyFieldset’ ,
‘options’ => array(
‘use_as_base_fieldset’ => true
)
)
);
$this->add(
array(
‘name’ => ‘btnsubmit’,
‘options’ => array(
‘label’ => ‘Submit’
),
‘attributes’ => array(
‘class’ => ‘btn’,
‘type’ => ‘submit’
)
)
);
$this->add(
array(
‘type’ => ‘Zend\Form\Element\Csrf’,
‘name’ => ‘security’,
)
);
$this->setValidationGroup(
array(
‘security’,
‘my-fieldset’=> array (
‘sex’,
‘name’
)
)
);
}
}
|
Primeiro detalhe importante a se perceber é a indicação de qual fieldset será trabalhado nesse form através do método add(), antes usado apenas para inserir campos. Repare que no vetor de configuração estamos informando que o fieldset inserido será o fieldset base do formulário.
O segundo detalhe importante é no método setValidationGroup(), que recebe os campos do formulários, mas com intermédio do fieldset, ou seja, o nome dos campos estarão dentro do vetor do fieldset.
Bind
Depois de todas essas alterações e de definirmos consequentemente melhor o contexto de cada classe, fieldset com os campos, entity com as validações, form ligando todos em um único local e gerando grupos de validação, podemos realizar um trabalho mais limpo em um controller.
Depois dessa organização ainda é possível fazermos um vínculo entre uma entidade nossa e o formulário, mesmo que ela esteja vinculada ao fieldset base do form e não ao form diretamente. A diferença é que o formulário não encontrando um objeto vinculado a ele em suas configurações, ele irá procurar em seu base fieldset e usá-lo.
Então, com o uso de fieldsets ligados ao form, o método bind() espera a entidade relacionada ao fieldset base, não suportando vínculo com entidade dos possíveis demais fieldsets. E com esse vínculo, o form que fará a população dos atributos da entidade com os valores de seu campo caso o método isValid() retorne verdadeiro.
Segue abaixo um exemplo do procedimento de binding em um controller.
class ExampleController extends AbstractActionController
{
public function indexAction() {
$form = new Form\MyForm
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
//Entidade populada com os campos do formulário.
}
}
return array(‘form’ => $form);
}
}
|
Outro detalhe é que quando nossa entidade possui valores em seus atributos, esse valores são adotados nos respectivos campos do formulário quando fazemos bind. Suponha que uma instância da nossa entidade receba no atributo $name o valor “Tássio” e no atributo $surname o valor “Auad”. Agora imagine que vamos vincular essa entidade ao formulário, e a consequência disso é o form tendo o campo com nome “name” preenchido pelo valor “Tássio” e o campo com nome “surname” preenchido com o valor “Auad”.
$entity = new Entity\MyEntity();
$entity->setName(“Tássio”);
$entity->setSurname(“Auad”);
$form = new Form\MyForm();
$form->bind($entity); //Formulário tendo os seus campos preenchidos
|
Atributos em camelCase
É importante lembra que o bind não considera que os atributos da entidade estejam em cameCase, mas sim com uso de “_” ou sem letras maiúsculas. O problema na verdade não está nos atributos, mas nos getters e setters que representam lidam com os atributos.
Um exemplo do que está sendo comentado é que um campo do formulário chamado fullName terá seu valor passado à entidade por meio do método getFullname(), desconsiderado a variação de maiúscula e minúscula do nome do campo.
setData()
O método para se popular um formulário com um vetor de valores é através do método setData(). Esse método relacionará as keys do vetor com nome dos campos e copiará os valores do vetor para os campos. Lembrando que o mesmo pode acontecer através do bind(), porém, não será com um vetor, mas com um objeto. Vamos ver um exemplo.
$form->setData(
array(
‘name’ => ‘Tássio’
‘surname’ => ‘Auad’
)
)
|
Quando exibirmos esse formulário em uma view, o campo name estará preenchido com o valor ‘Tássio’ e o campo surname, com o valor ‘Auad’.
isValid() e o InputFilter
Uma dúvida que pode ser gerada seria qual o InputFilter usado para a validação no método isValid() caso eu tenha um na entidade e outro no formulário? Qual tem a preferência? A resposta é a entidade.
Sempre que chamamos o método isValid() de um form, ele irá buscar na entidade em que foi feito o bind o seu InputFilter, caso não haja, ele irá procurar o InputFilter no Fieldset, e mesmo assim se não houver, ele irá instânciar um InputFilter.
Com o seu filter pronto, o método irá enviar a ele os valores dos campos e chamar o método isValid() do filter. Ou seja, esse método serve apenas para encapsular o processo de utilização ou criação de um InputFilter, passando a ele valores e verificando quais estão no ValidationGroup, e dos que estão, se todos são válidos.
Caso seja válido, a entidade que sofreu bind com o form será hidratada com os valores do formulário, e podemos assim, utilizá-la para inserir no banco de dados.