PHPackages                             kynx/laminas-form-shape - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. kynx/laminas-form-shape

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

kynx/laminas-form-shape
=======================

Generate Psalm types for Laminas forms

0.7.2(4mo ago)22.5k↑131.3%[4 issues](https://github.com/kynx/laminas-form-shape/issues)[2 PRs](https://github.com/kynx/laminas-form-shape/pulls)BSD-3-ClausePHPPHP ~8.3.0 || ~8.4.0 || ~8.5.0CI passing

Since Jan 31Pushed 1w ago2 watchersCompare

[ Source](https://github.com/kynx/laminas-form-shape)[ Packagist](https://packagist.org/packages/kynx/laminas-form-shape)[ RSS](/packages/kynx-laminas-form-shape/feed)WikiDiscussions main Synced today

READMEChangelog (10)Dependencies (15)Versions (31)Used By (0)

kynx/laminas-form-shape
=======================

[](#kynxlaminas-form-shape)

[![Continuous Integration](https://github.com/kynx/laminas-form-shape/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/kynx/laminas-form-shape/actions/workflows/continuous-integration.yml)

Generate [Psalm](https://psalm.dev) types for [Laminas forms](https://docs.laminas.dev/laminas-form/)

Installation
------------

[](#installation)

Install this package as a development dependency using [Composer](https://getcomposer.org):

```
composer require --dev kynx/laminas-form-shape vimeo/psalm

```

If you would rather use the [Psalm Phar](https://github.com/vimeo/psalm/blob/5.x/docs/running_psalm/installation.md#using-the-phar), require that instead:

```
composer require --dev kynx/laminas-form-shape psalm/phar

```

Usage
-----

[](#usage)

```
vendor/bin/laminas form:psalm-type src/Forms/Artist.php

```

...will add an [array shape](https://psalm.dev/docs/annotating_code/type_syntax/array_types/#array-shapes) to your `Artist` form something like:

```
 use Laminas\Form\Element\Text;
 use Laminas\Form\Form;

+/**
+ * @psalm-import-type TAlbumData from Album
+ * @psalm-type TArtistData = array{
+ *     id:     int|null,
+ *     name:   non-empty-string,
+ *     albums: array,
+ * }
+ * @extends Form
+ */
 final class Artist extends Form
 {
     /**
```

To see a full list of options:

```
vendor/bin/laminas form:psalm-type --help

```

Why?
----

[](#why)

Version [3.17.0](https://github.com/laminas/laminas-form/releases/tag/3.17.0) of Laminas Form introduced a Psalm template for describing the array returned by `Form::getData()`. This is a great improvement, and should winkle out potential bugs for anyone using Psalm. However generating the array shape for a form is a chore, and very easy to get wrong. Do you actually know the Psalm type produced by every combination of `required`, `allow_empty` and `continue_if_empty`? And what about all the possible filters and validators attached to the `InputFilter`?

This command introspects your form's input filter and generates the most specific array shape possible.

Configuration
-------------

[](#configuration)

All configuration is stored under the `laminas-form-shape` configuration key. The examples below assume you are using PHP configuration files that return arrays something like:

```
return [
    'laminas-form-shape' => [
        // custom config here
    ],
];
```

### Output formatting

[](#output-formatting)

There are three top level settings to control how the array shapes are formatted:

1. `indent` - string to use for indentation when pretty-printing array shapes. Default to four spaces.
2. `max-string-length` - maximum number of characters to include when outputting literal strings. Defaults to whatever psalm is configured with (typically 1000 characters).
3. `literal-limit` - maximum number of literals to output. If your `AllowList` filters or `InArray` validators contain large number of items you may want to limit this. Defaults to 100.

Changing the last two will make your array shapes less exact, but more readable.

```
return [
    'laminas-form-shape' => [
        'indent'             => "\t", // use tab for indenting
        'max-string-length'  => 50,   // don't output long literal strings
        'literal-limit'      => 20,   // don't output too many literals
    ],
];
```

### File elements

[](#file-elements)

File elements are used for handling \[form uploads\]. The `FileInput` validates both the array notation used by `Laminas\Http\PhpEnvironment\Request` and the PSR-7 `UploadFileInterface` used by applications such as Mezzio. It's highly likely your controllers / handlers will only be using one of these. If so, specify which one in the configuration:

```
return [
    'laminas-form-shape' => [
        'input' => [
            'file' => [
                // Laminas MVC applications
                'laminas' => true,
                'psr-7'   => false,
                // Mezzio applications
                // 'laminas' => false,
                // 'psr-7'   => true,
            ],
        ],
    ],
];
```

### Filters

[](#filters)

Each filter defined in your form's `InputFilter` will be processed by a number of [visitors](https://en.wikipedia.org/wiki/Visitor_pattern). Each visitor takes the previous list of types (typically starting with `null|string`) and adds or removes types depending on what the filter actually does.

Most filter visitors require no configuration, and for those that do we provide sensible defaults. But feel free to tweak the following:

#### AllowList

[](#allowlist)

The [AllowList](https://docs.laminas.dev/laminas-filter/v2/standard-filters/#allowlist) filter enables you to configure a list of terms that are "allowed" by the filter, or `null` if none match. We turn this into a literal list like `'first'|'second'|1|2|null`.

If there are no terms in the list we pass on whatever type the previous visitor output. This is so code that dynamically populates the list (for example, from a database query) does not barf.

You can change both behaviours via configuration:

```
return [
    'laminas-form-shape' => [
        'filter'             => [
            'allow-list' => [
                'allow-empty-list' => false, // empty lists will produce "Cannot get type" error
            ],
        ],
    ],
];
```

#### Callback

[](#callback)

We introspect the return type of the `callback` option of any callback [Callback filters](https://docs.laminas.dev/laminas-filter/v2/standard-filters/#callback) you use. Whatever types are discovered there are passed on to the next filter.

If you are doing anything more complicated than an `intval()`, I strongly encourage you to refactor these into implementations of `FilterInterface` and write a custom visitor to provide the specific types it returns: it will make your tests cleaner and your types more sound.

If your callbacks don't even have return types, they will be ignored. Fix them.

### Validators

[](#validators)

Each validator defined in your form's `InputFilter` will be processed by a number of [visitors](https://en.wikipedia.org/wiki/Visitor_pattern). The final list of types produced by all the filters is fed to the first, then the output of that is fed to the next, and so on. Validators typically narrow the final type. For instance, the visitor for a `Digits` validator will turn `string` types into `numeric-string`[1](#user-content-fn-1-80bf6f9220169bdff0bab460f3a8e4ce).

Most filter visitors require no configuration, and for those that do we provide sensible defaults. But feel free to tweak the following:

#### Callback

[](#callback-1)

[Callback validators](https://docs.laminas.dev/laminas-validator/validators/callback/) are ignored. Unlike callback filters, there is no reliable way to determine what they do. If you care about types (and if you are here, you do), convert them to concrete `ValidatorInterface` implementations and write a custom visitor to describe them. Your code - and your life - will be better for it.

#### File

[](#file)

The various [File](https://docs.laminas.dev/laminas-filter/v2/file/) validators accept an array from the `$_FILES` super-global or an `UploadedFileInterface`. We have a single visitor for handling them all.

If you've got a custom file validator that accepts the same, add it to the list of validators the visitor handles:

```
use MyApp\Validator\MyCustomFileValidator;

return [
    'laminas-form-shape' => [
        'validator' => [
            'file' => [
                'validators' => [
                    MyCustomFileValidator::class,
                ],
            ],
        ],
    ],
];
```

#### InArray

[](#inarray)

Like the `AllowList` filter, the [InArray](https://docs.laminas.dev/laminas-validator/validators/in-array/) validator accepts a list - or `haystack` - of values and verifies the input is one of them. The visitor can return a literal type (`'first'|'second'|1|2`). By default it ignores an empty haystack.

You can change the defaults:

```
return [
    'laminas-form-shape' => [
        'validator' => [
            'in-array' => [
                'allow-empty-haystack' => false, // empty haystacks will produce "Cannot get type" error
            ],
        ],
    ],
];
```

#### Regex

[](#regex)

The [Regex](https://docs.laminas.dev/laminas-validator/validators/regex/) validator rejects input that doesn't match its regular expression. If I were a genius and had time on my hands, I might be able to write a regex parser that could work out the type from the expression. But I'm not and I don't.

Instead we provide a list of known regular expressions used by standard form elements in the configuration. If you've got your own regular expressions you'll want to add them to the list.

The configuration is keyed by the regular expression string, and contains a list of [Psalm types](https://psalm.dev/docs/running_psalm/plugins/plugins_type_system/#creating-type-object-instances-directly) that are used to narrow the type union. For instance, the `Number` element will validate a `float`, `int` or `numeric-string`. It's configuration looks like:

```
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TString;

return [
    'laminas-form-shape' => [
        'validator' => [
            'regex' => [
                '(^-?\d*(\.\d+)?$)' => [TFloat::class, TInt::class, TNumericString::class],
            ],
        ],
    ],
];
```

#### Strings

[](#strings)

There are a large number of validators that do clever things to verify the format of strings, from [Barcode](https://docs.laminas.dev/laminas-validator/validators/barcode/) to [Uuid](https://docs.laminas.dev/laminas-validator/validators/uuid/). But all they tell us about the type is to change `string` to `non-empty-string`.

If you have a custom validator that does the same, add it to the list:

```
use MyApp\Validator\MyCustomStringValidator;

return [
    'laminas-form-shape' => [
        'validator' => [
            'non-empty-string' => [
                'validators' => [
                    MyCustomStringValidator::class,
                ],
            ],
        ],
    ],
];
```

Beware
------

[](#beware)

If your code changes forms at run time - adding and removing elements, changing `required` properties or populating `` options - this tool won't know. The array shape it generates can serve as a good starting point, but will need manual tweaks.

This tool aims to cover all the filters or validators installed when you `composer require laminas/laminas-form`. If it encounters one it doesn't know about, it silently ignores it. See the [Customisation](#custom-filters-and-validators) section for pointers on handling these.

### `Cannot get type for 'foo'`

[](#cannot-get-type-for-foo)

It is possible to build an input filter with a combination of filters and validators that can never produce a result. For instance, a `Boolean` filter with the `casting` option set to `true` will ony ever output a `bool` type. If you follow that with a `Barcode` validator, the element can never validate. When the command encounters a situation like that it will report a "Cannot get type" error.

If you see this error when parsing an existing form that has been functioning fine for years, you've hit a bug. Please raise an issue with a *small* example form that reproduces the error. Or, better yet, create a PR with a failing test 😃

Custom filters and validators
-----------------------------

[](#custom-filters-and-validators)

To come, once things settle down...

Footnotes
---------

1. The final type decided on by all the filters and validators isn't the end of it. Depending on how the `Input`itself is configured, the type may be broadened. For instance, if you `allow_empty`, a `non-empty-string` will be broadened back to just `string`. [↩](#user-content-fnref-1-80bf6f9220169bdff0bab460f3a8e4ce)

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance68

Regular maintenance activity

Popularity23

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 66.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~34 days

Recently: every ~178 days

Total

23

Last Release

138d ago

PHP version history (2 changes)0.1.0PHP ~8.2 || ~8.3

0.7.1PHP ~8.3.0 || ~8.4.0 || ~8.5.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/a11d32344c8f2811ccd9930cbf5c9191039e2f063f547dcbcc1bb9ec30843376?d=identicon)[kynx](/maintainers/kynx)

---

Top Contributors

[![kynx](https://avatars.githubusercontent.com/u/1320145?v=4)](https://github.com/kynx "kynx (45 commits)")[![renovate[bot]](https://avatars.githubusercontent.com/in/2740?v=4)](https://github.com/renovate[bot] "renovate[bot] (23 commits)")

---

Tags

laminasdevformpsalm

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/kynx-laminas-form-shape/health.svg)

```
[![Health](https://phpackages.com/badges/kynx-laminas-form-shape/health.svg)](https://phpackages.com/packages/kynx-laminas-form-shape)
```

###  Alternatives

[symfony/symfony

The Symfony PHP framework

31.4k87.2M2.2k](/packages/symfony-symfony)[symfony/web-profiler-bundle

Provides a development tool that gives detailed information about the execution of any request

2.3k160.5M1.2k](/packages/symfony-web-profiler-bundle)[pocketmine/pocketmine-mp

A server software for Minecraft: Bedrock Edition written in PHP

3.5k78.3k90](/packages/pocketmine-pocketmine-mp)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

751291.4k43](/packages/civicrm-civicrm-core)[flow-php/etl

PHP ETL - Extract Transform Load - Abstraction

378604.0k104](/packages/flow-php-etl)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
