Skip to content

Commit

Permalink
Merge pull request #117 from gsteel/v3/prepare-docs
Browse files Browse the repository at this point in the history
Prepare docs for version 3
  • Loading branch information
gsteel authored Jan 14, 2025
2 parents 6798630 + 2b1dde8 commit feb484a
Show file tree
Hide file tree
Showing 21 changed files with 1,188 additions and 13 deletions.
File renamed without changes.
File renamed without changes.
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

0 comments on commit feb484a

Please sign in to comment.