Skip to content

Updated upstream and added Nette Forms 3.3 compatibility - #1

Merged
slischka merged 44 commits into
masterfrom
codex/update-upstream-and-nette-forms-3-3
Jul 24, 2026
Merged

Updated upstream and added Nette Forms 3.3 compatibility#1
slischka merged 44 commits into
masterfrom
codex/update-upstream-and-nette-forms-3-3

Conversation

@slischka

Copy link
Copy Markdown
Member

What changed

  • synchronized the FAPI fork with the current contributte/forms-multiplier master
  • preserved the public Iterator return type while adapting getControls() to the repeatable iterable returned by Nette Forms 3.3
  • added a regression test for the getControls() return type
  • constrained test-only dependencies to versions compatible with the test helper

Why

Nette Forms 3.3 changed Container::getControls() from an Iterator to a repeatable iterable. The multiplier still declared Iterator and returned the parent result directly, causing a runtime TypeError whenever a multiplier form was submitted.

Validation

  • test suite with Nette Forms 3.3.0
  • test suite with the lowest supported Nette Forms 3.2.x dependencies
  • PHP_CodeSniffer
  • PHPStan with the lowest supported dependencies
  • strict Composer validation

Attanon and others added 30 commits November 21, 2023 08:51
PHP 8.2 
Declaration of Contributte\FormMultiplier\Multiplier::setValues($values, bool $erase = false): Contributte\FormMultiplier\Multiplier must be compatible with Nette\Forms\Container::setValues(object|array $data, bool $erase = false): static
`Container::getComponents()` returns array when `$deep` is false (by default):
nette/component-model@7f613ee

The method also no longer takes any arguments:
nette/component-model@4e0946a
Pressing “Create” inside a nested multiplier with fields with default values
did not work before 39725d3.
This has been reported in the first bullet point in
contributte#56

The second bullet point is already covered by `testSendNested`
change introduced in the fix.
This fixes the following PHPStan errors:

  280    Method Contributte\FormMultiplier\Multiplier::getValues() has parameter $returnType with no type specified.
  290    No error to ignore is reported on line 290.

Let’s also deprecate passing bool `$returnType` deprecated like `nette/forms` 3.2.0 does:

nette/forms@0a812bd

Also use Array constant made public in 3.1.12:

nette/forms@0be7b3d
nette/component-model 3.1.0 made the type annotations more precise,
correctly declaring that `null` will not be returned when `$throw` argument is `true` (default):
nette/component-model@fbab7bc

As a result PHPStan started to complain:

    Expression on left side of ?? is not nullable.
There are no acceptance tests and `make coverage` would fail with:

    Code coverage file /home/runner/work/forms-multiplier/forms-multiplier/tests/_output/c3tmp/codecoverage.serialized does not exist
`make coverage` would fail with:

    No code coverage driver available

Sounds like it might have been broken by xdebug 2 → 3 bump:
https://xdebug.org/docs/upgrade_guide#New-Concepts

Cargo culted from contributte/invoice@ca51236
nette/forms@e227d87 added an extra argument.

While at it, let’s also narrow the `$values` argument type to match `Container::setValues`.

This change remains compatible with nette/forms 3.2.2.
This was accidentally deleted in 1d9a356
nette/forms@0cd5069 added more types but that is overly broad for our usage pattern. We need to cast it to proper value until the return type is clarified upstream.

Also use `$this->getForm()` instead of `$this->form` since the former is already checked not to be `null` by `$this->isFormSubmitted()`.
This was accidentally broken in 45cf2a8
This partially reverts commit 39725d3.

The commit introduced a regression in `testGroupManualRenderWithButtons`,
changed the internal architecture in a way that made it harder to reason about
(the Multiplier inconsistently took over some responsibilities of ComponentResolver).
Additionally, it applied the following changes that are unrelated
to the purported fix and not really necessary:

- Latte 2 macros are trivial to migrate and unclearly named, no need to keep them for BC.
- The onSuccess handlers were already fixed in ed96ba0.
- `testGroupManualRenderWithButtons` look like remnants of debugging effort, they do not fix the test.

We are only keeping the following:

- Test for nested support (to be implemented later).
- Fix for `assertMatchesRegularExpression` deprecation in PHPUnit.

For reference, the reverted change is the following commit that was squashed
into 39725d3, except for the change to `testSendNested`:
contributte@2c33de2
In bde2503, we started to require component-model 3.1.0, which requires PHP 8.1.
As a result, CI can no longer build on PHP 8.0.
Let’s bump the PHP requirement to match.
nette/forms 3.1.15 clarified that the `Container::getForm` method can only return `null` when passed `throw: false`:
nette/forms@ee43bfc

With PHPStan 2.0 this would be noticed and result in an error:

    384    Strict comparison using !== between Nette\Forms\Form and null will always evaluate to   true.
           🪪  notIdentical.alwaysTrue
Let’s use non-throwing variant and also add the PHPDoc to be explicit.
45ed76a started filtering components
to `Control`s, to appease PHPStan, since `Container::validate()` only
accepted `Control[]`. But `Multiplier` does not actually have `Control`s
as direct children (other than the ‘Add’ `Submitter`s), so it would stop
validating and filtering multiplied controls.

a5a7348 reverted that part but kept
the incorrect phpdoc type cast.

Now, it works without the filter because `Container::validate()` already
ignores non-validatable components but we should still respect its contract.

Let’s filter the components before passing them down. This will also
allow us to drop the lying phpdoc type cast.

nette/forms 3.2.2 updated its phpdoc param type to allow that:
nette/forms@6437671
Filters are used for converting an input value (usually a string)
into a domain specific PHP type and they are bound to the validation scope:
https://doc.nette.org/en/forms/validation#toc-modifying-input-values

45ed76a started filtering components
passed to `Container::validate()` to `Control`s, to appease PHPStan.
But `Multiplier` does not actually have `Control`s as direct children
(other than the ‘Add’ `Submitter`s), so it would stop validating and
filtering multiplied controls.

It has been since fixed in a5a7348,
and more properly in the parent commit but let’s add a test so this
does not happen again.

It can be reproduced by removing `|| $component instanceof Container`
from the parent commit.
It was changed to array in bde2503 to override incorrect return value from phpstan-nette’s stub.
But with upgrade to PHPStan 2.0 in 07a1009, it is no longer helping – we had to resort to ignoring the error in 9533530.
jtojnar and others added 13 commits June 7, 2025 20:45
This was introduced in a2ccdcf (contributte#83) but has not been fixed so far.

When a group is created inside the modifier, clicking the create button will cause one more fieldset to be created than there are containers:

```html
  <form action="/" method="post" id="frm-form">

  <fieldset>
  <legend>Team member #1</legend>

  <table>
  <tr>
  	<th><label for="frm-form-members-0-name">Name</label></th>

  	<td><input type="text" name="members[0][name]" id="frm-form-members-0-name" class="text"></td>
  </tr>
  </table>
  </fieldset>

  <fieldset>
  <legend>Team member contributte#2</legend>

  <table>
  <tr>
  	<th><label for="frm-form-members-1-name">Name</label></th>

  	<td><input type="text" name="members[1][name]" id="frm-form-members-1-name" class="text"></td>
  </tr>
  </table>
  </fieldset>

  <fieldset>
  <legend>Team member contributte#3</legend>
  </fieldset>

  <table>
  <tr>
  	<th></th>

  	<td><input type="submit" name="members[multiplier_creator]" class=" button" value="add" formnovalidate data-nette-validation-scope='["members"]'></td>
  </tr>
  </table>

  <input type="hidden" name="_do" value="form-submit">
  </form>
```

```
 Test  tests/unit/CreateButtonTest.php:testFieldsets
After adding a container, there should be two fieldsets.
Failed asserting that actual size 3 matches expected size 2.
```
The nested multipliers were fixed in 39725d3 but the change broke other features so it was reverted in 71cc347 (keeping the test change since it was what we want). See contributte#92 for some more details.

Another regression test was introduced in 1e6e3b2 to ensure the nested multiplier support is correctly implemented.

However, proper implementation has not manifested so far so let’s mark the tests as incomplete until then.
…erty

PHPStan 2.1.8 started to complain about `property.protected`:

    Access to protected property Nette\Forms\Container::$currentGroup.

See phpstan/phpstan#13123

Fortunately, Nette provides a public method we can use instead.
Though they are currently broken by something else so marking them as ignored.
This would have caused an exception anyway since the `removeButton` property would have been `null`:

    [Error] Call to a member function create() on null
This was fixed in 9e0847c but there was no test.

Also add a comment since the code is non-obvious.
Currently, it is allowed so mark the test as ignored.
@slischka
slischka marked this pull request as ready for review July 24, 2026 10:13
# Conflicts:
#	composer.json
@slischka
slischka merged commit 9918299 into master Jul 24, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants