laminas / tutorials

Learn how to create laminas-mvc applications, get in-depth guides into components, and discover how to migrate your applications to version 3!
https://docs.laminas.dev/tutorials/
Other
33 stars 37 forks source link

Add input filter validation to fieldset tutorial #68

Open Redcentaur opened 3 months ago

Redcentaur commented 3 months ago

In the in-depth tutorial you add the use of laminas-form and laminas-form-fieldset and in the Controller check for validity of the inputs. In the conclusion of that section, it is claimed that we've learnt how to validate input but we haven't: the input to the fields is not validated because we haven't created an input filter or attached any validators to the form or to the fieldset containing the form data.

I think it is particularly important to cover this topic as you are using fieldset in the form for the object hydrator and this can make setting data on the form and collecting data back from valid forms difficult with input filters. Can you cover it in more detail please?

gsteel commented 3 months ago

Thanks for the report @Redcentaur - you are welcome to send a patch to improve the docs if you are able… It could be a case of adding links to other existing docs in the pertinent places.

Redcentaur commented 3 months ago

@gsteel I'd love to be in a position to help and perhaps one day soon I will be able to, but I don't think I know enough about Laminas at this stage to add to the tutorial competently (which is why I'm doing the tutorial :) )

gsteel commented 3 months ago

👍 - Good luck with it, and don't forget the forum and slack if you run into any trouble :)

froschdesign commented 2 months ago

the input to the fields is not validated because we haven't created an input filter or attached any validators to the form or to the fieldset containing the form data

Good point because the used form elements Text and Textarea do not come with validators like Email, MultiCheckbox, DateSelect or Number.

Redcentaur commented 2 months ago

the input to the fields is not validated because we haven't created an input filter or attached any validators to the form or to the fieldset containing the form data

Good point because the used form elements Text and Textarea do not come with validators like Email, MultiCheckbox, DateSelect or Number.

No they don't, but they can and should still have filters (StripTags/StringTrim) and validators (StringLength) etc relevant to the expected input.

What I'm not sure about is how to inject the inputFilter for the form, set the data on it, validate the data and retrieve the data from it in the Controller when the form has a fieldset. I get form validation errors saying the fields are required and cannot be empty, when they have been populated.

froschdesign commented 2 months ago

No they don't, but they can and should still have filters (StripTags/StringTrim) and validators (StringLength) etc relevant to the expected input.

Correct and I didn't want to doubt this, on the contrary, this part is missing in the tutorial. I will add this part.

What I'm not sure about is how to inject the inputFilter for the form, set the data on it, validate the data and retrieve the data from it in the Controller when the form has a fieldset.

There are some more tutorials: "Application Integrations"

For example:

Redcentaur commented 2 months ago

Hi Frank

I have done some research and got an InputFilter working with the tutorial code. I'm not sure if this is best practice or not?

First, in the Post.php file, add the references, Interface implementation and create a parameter to hold the InputFilter:

// module/Blog/Model/Post.php

use DomainException;
use Laminas\Filter\StringTrim;
use Laminas\Filter\StripTags;
use Laminas\Validator\StringLength;
use Laminas\Filter\ToInt;
use Laminas\InputFilter\InputFilterAwareInterface;
use Laminas\INputFilter\InputFilterInterface;

class Post implements InputFilterAwareInterface
{
    /* ... */

    /**
     *  @var InputFilterInterface
     */
    private $inputFilter;

    /* ... */

Then create the InputFilter setter and getter methods at the end of the Post class:

// module/Blog/Model/Post.php

/* ... */

public function setInputFilter(InputFilterInterface $inputFilter)
{
    throw new DomainException(sprintf(
        '%s does not allow the injection of an alternative input filter'
        , __CLASS__
    ));
}

public function getInputFilter()
{
    if ($this->inputFilter) {
        return $this->inputFilter;
    }

    $inputFilter = new InputFilter();

    $inputFilter->add([
        'name' => 'id',
        'required' => true,
        'filters' => [
            ['name' => ToInt::class],
        ],
   ]);

    $inputFilter->add([
        'name' => 'title',
        'required' => true,
        'filters' => [
            ['name' => StripTags::class],
            ['name' => StringTrim::class],
        ],
        'validators' => [
            [
                'name' => StringLength::class,
                'options' => [
                    'encoding' => 'UTF-8',
                    'min' => 1,
                    'max' => 100,
                ],
            ],
        ],
   ]);

    $inputFilter->add([
        'name' => 'text',
        'required' => true,
        'filters' => [
            ['name' => StripTags::class],
            ['name' => StringTrim::class],
        ],
        'validators' => [
            [
                'name' => StringLength::class,
                'options' => [
                    'encoding' => 'UTF-8',
                    'min' => 1,
                    'max' => 2000,
                ],
            ],
        ],
   ]);

    $this->inputFilter = $inputFilter;

    return $this->inputFilter;
}

Next, in the WriteController, we need to add the Post reference at the top of the file:

use Blog\Model\Post;

Then we need to alter the addAction() to create a new Post entity. We do this so we can call the InputFilter from the model.

// module/Blog/src/Controller/WriteController.php

/* ... */

// In public function addAction()
// change this line:
// $post = $this->form->getData();
$post = new Post('', '');

Now we can set the form's InputFilter using the InputFilter get method from the Post entity. Because we used a Fieldset in our form, we need to set a validation group on the form, so that the InputFilter is applied to the correct fieldset:

$post = new Post('', '');

$this->form->setInputFilter($post->getInputFilter());
$this->form->setValidationGroup(['post' => ['title', 'text']]);
$this->form->setData($request->getPost());

/* ... */

The setValidationGroup() method takes an associative array using the name of the fieldset as its index ('post') associated with an array of the fields that are present to be validated and filtered (in this case, 'title' and 'text'). Note, because 'id' is not present in the addAction() form, we have to leave it out, otherwise the form will never validate because the id is not present and does not contain a value.

Finally, for addAction(), we can retrieve the data and save it to the database:

/* ... */
try {
    $data = $this->form->getData();
    $post = $this->command->insertPost($data);
} catch (Exception $e) {
    throw $e;
}
/* ... */

In the editAction(), we already have a Post entity, which we bound to the form, so all we need to do is retrieve the InputFilter from it and set it on the form, set the data and eventually post it:

// module/Blog/src/Controller/WriteController.php
// public function editAction():

if (! $request->isPost()) {
    return $viewModel;
}

// ADD the following three lines:
$this->form->setInputFilter($post->getInputFilter());
$this->form->setValidationGroup(['post' => ['id', 'title', 'text']]);
$this->form->setData($request->getPost());

/* ... */

// ... and finally in the try/catch statement, change the updatePost line:
$post = $this->command->updatePost($this->form->getData());

Is this correct and does it make sense?

froschdesign commented 2 months ago

Is this correct and does it make sense?

Too complicated. 😜

Define the input filter in a form and you are done. Follow the description I have given you above: https://docs.laminas.dev/laminas-inputfilter/cookbook/input-filter-in-forms/#define-the-input-filter-in-a-form

You will find the same procedure in the documentation of laminas-form: https://docs.laminas.dev/laminas-form/v3/application-integration/usage-in-a-laminas-mvc-application/


For further questions, please use our forum or the chat.