Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare docs for version 3 #117

Merged
merged 1 commit into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Usage in a laminas-mvc Application

The following example shows _one_ potential use case of laminas-inputfilter within a laminas-mvc based application.
The example uses a module, a controller and the input filter plugin manager.

The example is based on the [tutorial application](https://docs.laminas.dev/tutorials/getting-started/overview/) which builds an album inventory system.

Before starting, make sure laminas-inputfilter is [installed and configured](../installation.md).

## Create Input Filter

[Create an input filter as separate class](../intro.md) using the `init` method, e.g. `module/Album/src/InputFilter/QueryInputFilter.php`:

```php
namespace Album\InputFilter;

use Laminas\Filter\ToInt;
use Laminas\I18n\Validator\IsInt;
use Laminas\InputFilter\InputFilter;

final class QueryInputFilter extends InputFilter
{
public function init(): void
{
// Page
$this->add(
[
'name' => 'page',
'allow_empty' => true,
'validators' => [
[
'name' => IsInt::class,
],
],
'filters' => [
[
'name' => ToInt::class,
],
],
'fallback_value' => 1,
]
);

// …
}
}
```

## Create Controller

[Create a controller class](https://docs.laminas.dev/laminas-mvc/quick-start/#create-a-controller) and inject the input filter plugin manager via the constructor, e.g. `module/Album/Controller/AlbumController.php`:

```php
namespace Album\Controller;

use Album\InputFilter\QueryInputFilter;
use Laminas\InputFilter\InputFilterPluginManager;
use Laminas\Mvc\Controller\AbstractActionController;

use function assert;

final class AlbumController extends AbstractActionController
{
public function __construct(
public readonly InputFilterPluginManager $inputFilterPluginManager
) {}

public function indexAction()
{
$inputFilter = $this->inputFilterPluginManager->get(QueryInputFilter::class);
assert($inputFilter instanceof QueryInputFilter);

$inputFilter->setData($this->getRequest()->getQuery());
$inputFilter->isValid();
$filteredParams = $inputFilter->getValues();

// …
}
}
```

> INFO: **Instantiating the Input Filter**
>
> The input filter plugin manager (`Laminas\InputFilter\InputFilterPluginManager`) is used instead of directly instantiating the input filter to ensure to get the filter and validator plugin managers injected.
> This allows usage of any filters and validators registered with their respective plugin managers.
>
> Additionally, the input filter plugin manager calls the `init` method _after_ instantiating the input filter, ensuring all dependencies are fully injected first.

## Register Input Filter and Controller

If no separate factory is required for the input filter, then the input filter plugin manager will be instantiating the input filter class without prior registration. Otherwise, the input filter must be registered.

To [register the controller](https://docs.laminas.dev/laminas-mvc/quick-start/#create-a-route) for the application, extend the configuration of the module.
Add the following lines to the module configuration file, e.g. `module/Album/config/module.config.php`:

<!-- markdownlint-disable MD033 -->
<pre class="language-php" data-line="8-9"><code>
namespace Album;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

return [
'controllers' => [
'factories' => [
// Add this line
Controller\AlbumController::class => ReflectionBasedAbstractFactory::class,
],
],
// …
];
</code></pre>
<!-- markdownlint-enable MD033 -->

The example uses the [reflection factory from laminas-servicemanager](https://docs.laminas.dev/laminas-servicemanager/reflection-abstract-factory/) to resolve the constructor dependencies for the controller class.
178 changes: 178 additions & 0 deletions docs/book/v3/application-integration/usage-in-a-mezzio-application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Usage in a mezzio Application

The following example shows _one_ potential use case of laminas-inputfilter within
a mezzio based application. The example uses a module, config provider
configuration, laminas-servicemanager as dependency injection container, the
laminas-inputfilter plugin manager and a request handler.

Before starting, make sure laminas-inputfilter is [installed and configured](../installation.md).

## Create Input Filter

Create an input filter as separate class, e.g.
`src/Album/InputFilter/QueryInputFilter.php`:

```php
namespace Album\InputFilter;

use Laminas\Filter\ToInt;
use Laminas\I18n\Validator\IsInt;
use Laminas\InputFilter\InputFilter;

class QueryInputFilter extends InputFilter
{
public function init()
{
// Page
$this->add(
[
'name' => 'page',
'allow_empty' => true,
'validators' => [
[
'name' => IsInt::class,
],
],
'filters' => [
[
'name' => ToInt::class,
],
],
'fallback_value' => 1,
]
);

// …
}
}
```

## Using Input Filter

### Create Handler

Using the input filter in a request handler, e.g.
`src/Album/Handler/ListHandler.php`:

```php
namespace Album\Handler;

use Album\InputFilter\QueryInputFilter;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Laminas\InputFilter\InputFilterInterface;
use Mezzio\Template\TemplateRendererInterface;

class ListHandler implements RequestHandlerInterface
{
/** @var InputFilterInterface */
private $inputFilter;

/** @var TemplateRendererInterface */
private $renderer;

public function __construct(
InputFilterInterface $inputFilter,
TemplateRendererInterface $renderer
) {
$this->inputFilter = $inputFilter;
$this->renderer = $renderer;
}

public function handle(ServerRequestInterface $request) : ResponseInterface
{
$this->inputFilter->setData($request->getQueryParams());
$this->inputFilter->isValid();
$filteredParams = $this->inputFilter->getValues();

// …

return new HtmlResponse($this->renderer->render(
'album::list',
[]
));
}
}
```

### Create Factory for Handler

Fetch the `QueryInputFilter` from the input filter plugin manager in a factory,
e.g. `src/Album/Handler/ListHandlerFactory.php`:

```php
namespace Album\Handler;

use Album\InputFilter\QueryInputFilter;
use Psr\Container\ContainerInterface;
use Laminas\InputFilter\InputFilterPluginManager;

class ListHandlerFactory
{
public function __invoke(ContainerInterface $container)
{
/** @var InputFilterPluginManager $pluginManager */
$pluginManager = $container->get(InputFilterPluginManager::class);
$inputFilter = $pluginManager->get(QueryInputFilter::class);

return new ListHandler(
$inputFilter,
$container->get(TemplateRendererInterface::class)
);
}
}
```

> ### Instantiating the InputFilter
>
> The `InputFilterPluginManager` is used instead of directly instantiating the
> input filter to ensure to get the filter and validator plugin managers
> injected. This allows usage of any filters and validators registered with
> their respective plugin managers.
>
> Additionally the `InputFilterPluginManager` calls the `init` method _after_
> instantiating the input filter, ensuring all dependencies are fully injected
> first.

## Register Input Filter and Handler

Extend the configuration provider of the module to register the input filter and
the request handler, e.g. `src/Album/ConfigProvider.php`:

```php
namespace Album;

class ConfigProvider
{
public function __invoke() : array
{
return [
'dependencies' => $this->getDependencies(),
'input_filters' => $this->getInputFilters(), // <-- Add this line
];
}

public function getDependencies() : array
{
return [
'factories' => [
Handler\ListHandler::class => Handler\ListHandlerFactory::class, // <-- Add this line
// …
],
];
}

// Add the following method
public function getInputFilters() : array
{
return [
'factories' => [
InputFilter\QueryInputFilter::class => InvokableFactory::class,
],
];
}

// …
}
```
72 changes: 72 additions & 0 deletions docs/book/v3/cookbook/enhanced-type-inference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Using Laminas InputFilter with Static Analysis Tools

It can be tedious to assert that the array returned by a given input filter contains the keys and value types that you would expect before using those values in your domain.
If you use static analysis tools such as Psalm or PHPStan, `InputFilterInterface` defines a generic template that can be used to refine the types you receive from the `getValues()` method.

```php
<?php

namespace My;

use Laminas\Filter\ToInt;
use Laminas\Filter\ToNull;
use Laminas\I18n\Validator\IsInt;
use Laminas\InputFilter\InputFilter;use Laminas\Validator\GreaterThan;

/**
* @psalm-type ValidPayload = array{
* anInteger: int<1, max>,
* }
* @extends InputFilter<ValidPayload>
*/
final class SomeInputFilter extends InputFilter
{
public function init(): void
{
$this->add([
'name' => 'anInteger',
'required' => true,
'filters' => [
['name' => ToNull::class],
['name' => ToInt::class],
],
'validators' => [
['name' => NotEmpty::class],
['name' => IsInt::class],
[
'name' => GreaterThan::class,
'options' => [
'min' => 1,
],
],
],
]);
}
}
```

With the above input filter specification, one can guarantee that, if the input payload is deemed valid, then you will receive an array with the expected shape from `InputFilter::getValues()`, therefore, your static analysis tooling will not complain when you pass that value directly to something that expects a `positive-int`, for example:

```php
/**
* @param positive-int $value
* @return positive-int
*/
function addTo5(int $value): int
{
return $value + 5;
}

$filter = new SomeInputFilter();
$filter->setData(['anInteger' => '123']);
assert($filter->isValid());

$result = addTo5($filter->getValues()['anInteger']);
```

## Further reading

- [Psalm documentation on array shapes](https://psalm.dev/docs/annotating_code/type_syntax/array_types/#array-shapes)
- [Psalm documentation on type aliases](https://psalm.dev/docs/annotating_code/type_syntax/utility_types/#type-aliases)
- [PHPStan documentation on array shapes](https://phpstan.org/writing-php-code/phpdoc-types#array-shapes)
- [PHPStan documentation on type aliases](https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases)
Loading
Loading