Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Form] Add BoundsType to render a lower and an upper bound - #65928

Open
seb-jean wants to merge 1 commit into
symfony:8.2from
seb-jean:feat/form-between-type
Open

[Form] Add BoundsType to render a lower and an upper bound#65928
seb-jean wants to merge 1 commit into
symfony:8.2from
seb-jean:feat/form-between-type

Conversation

@seb-jean

@seb-jean seb-jean commented Sep 9, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? 8.2
Bug fix? no
New feature? yes
Deprecations? no
Issues Fix #65927
License MIT

BoundsType renders a lower and an upper bound of the same inner type, the way RepeatedType renders the same type twice. The inner type is an option and carries its own configuration:

// two selects
$builder->add('age', BoundsType::class, [
    'type' => ChoiceType::class,
    'options' => ['choices' => $ages],
    'from_options' => ['label' => 'From'],
    'to_options' => ['label' => 'To'],
]);

// a price range, ordered by the type itself
$builder->add('price', BoundsType::class, [
    'type' => MoneyType::class,
    'options' => ['currency' => 'EUR'],
    'compare' => true,
]);

// a date range
$builder->add('publishedAt', BoundsType::class, [
    'type' => DateType::class,
]);

// two sliders: it composes with RangeType rather than duplicating it
$builder->add('budget', BoundsType::class, [
    'type' => RangeType::class,
]);

Options reach both bounds through options, and a single bound through from_options / to_options, rather than through a hand-maintained list of forwarded options as DateType does for its three children.

The bounds are two plain children named from and to, with no view transformer on the parent, so an array{from: mixed, to: mixed} property and an object exposing the two bounds both map, and keys the type knows nothing about survive a round trip. A model that names them otherwise is reached with property_path:

$builder->add('price', BoundsType::class, [
    'from_options' => ['property_path' => 'minPrice'],
    'to_options' => ['property_path' => 'maxPrice'],
]);

A range whose bounds are all empty reverts to null on submission, the way an entirely empty date does in DateType; a half-open range is meaningful and is kept as is.

compare checks the ordering. true orders scalars, DateTimeInterface and enums natively, enums following their declaration order, which is the order the inner type lists them in:

$builder->add('height', BoundsType::class, [
    'type' => EnumType::class,
    'options' => ['class' => Height::class],
    'compare' => true,
]);

The bounds are compared in their normalized form, the shape the inner type reasons in rather than the one the model ends up holding. A DateType is therefore ordered as a date whatever its input option turns it into, including string with an input_format that does not sort lexicographically, and a ChoiceType listing enum cases is ordered like EnumType.

Anything else takes a callable, since the component cannot order arbitrary inner types:

$builder->add('supported', BoundsType::class, [
    'type' => VersionType::class,
    'compare' => static fn (Version $from, Version $to): int => version_compare((string) $from, (string) $to),
]);

It reports compare_message on the lower bound, the one a reader has to move to make the range valid. Validation stays available for everything the type cannot express: BoundsTypeValidatorExtension maps what validation says about the range as a whole onto the lower bound too, the way RepeatedTypeValidatorExtension maps onto the first field.

Since the last round:

  • renamed from BetweenType to BoundsType (@yceruto)
  • from_name / to_name are gone: the children are always from and to, and property_path covers a model that names them otherwise (@stof)
  • added the compare option (@yceruto), which orders enums natively so that EnumType needs no callable
  • setAllowedValues('compound', true) rather than redefining the default, and a shorter docblock (@yceruto)

One point on compare I would like settled in review: true is inclusive, as the review asked for (from <= to). A strict from < to currently needs static fn ($from, $to): int => $from < $to ? -1 : 1, which reads poorly. Should the callable be a predicate returning whether the bounds are ordered, rather than a comparator?

@alexandre-daubois

alexandre-daubois commented Sep 9, 2026

Copy link
Copy Markdown
Member

I think the idea is nice but I'm not sold to the current name. Does a BoundariesType or something similar would work better?

Actually, you used range in your comments and it reminded me that there's already RangeType. Maybe we could improve it instead of creating a new type

@seb-jean
seb-jean force-pushed the feat/form-between-type branch from 44742cf to 2598dea Compare September 9, 2026 13:56
@alexandre-daubois

Copy link
Copy Markdown
Member

Also please note that new translation keys in .xlif files will be added in another PR targeting the oldest maintained branch (6.4 right now), not this one

@seb-jean

seb-jean commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Also please note that new translation keys in .xlif files will be added in another PR targeting the oldest maintained branch (6.4 right now), not this one

So I remove the src/Symfony/Component/Form/Resources/translations/* folder from my PR?

@alexandre-daubois

Copy link
Copy Markdown
Member

Yes, this PR should only use plain English values in your classes. If this gets accepted, you'll be able to create a new PR for translations. This eases our work of maintenance between branches

@seb-jean
seb-jean force-pushed the feat/form-between-type branch from 2598dea to 85196e6 Compare September 9, 2026 14:07
@seb-jean
seb-jean marked this pull request as draft September 9, 2026 14:08
@seb-jean
seb-jean force-pushed the feat/form-between-type branch from 85196e6 to d5c586c Compare September 9, 2026 14:39
@seb-jean seb-jean changed the title [Form] Add BetweenType to render a lower and an upper bound [Form] Add a "pair" widget to RangeType, to render a lower and an upper bound Sep 9, 2026
@seb-jean

seb-jean commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Actually, you used range in your comments and it reminded me that there's already RangeType. Maybe we could improve it instead of creating a new type

I actually opted for a new type of form since RangeType only returns a single value, I created BetweenType. I decided to go a different way than what you suggested.

@seb-jean
seb-jean marked this pull request as ready for review September 9, 2026 14:44
@seb-jean
seb-jean force-pushed the feat/form-between-type branch 2 times, most recently from c0bc059 to bbf348b Compare September 9, 2026 14:58
@stof

stof commented Sep 9, 2026

Copy link
Copy Markdown
Member

This does not belong to RangeType. Using an option to change between 2 totally unrelated form structures (one dealing with a single numeric value, the other one dealing with an array with from and to keys with comparable types) does not make sense. It makes this form type hard to document, hard to maintain and hard to customize in rendering.
That's a use case for a different form type. Defining the boundaries of a range of values is not the same than picking a value belonging to a range.
So -1 on this PR.

@seb-jean

seb-jean commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

This does not belong to RangeType. Using an option to change between 2 totally unrelated form structures (one dealing with a single numeric value, the other one dealing with an array with from and to keys with comparable types) does not make sense. It makes this form type hard to document, hard to maintain and hard to customize in rendering.
That's a use case for a different form type. Defining the boundaries of a range of values is not the same than picking a value belonging to a range.
So -1 on this PR.

Okay, I'm going to go back to how things were before, with a new type of form.

@alexandre-daubois

Copy link
Copy Markdown
Member

Oh yeah, that's right, I misremembered what RangeType is used for. Sorry for leading you in the wrong direction!

@seb-jean
seb-jean force-pushed the feat/form-between-type branch from bbf348b to f330bbe Compare September 9, 2026 19:39
@seb-jean seb-jean changed the title [Form] Add a "pair" widget to RangeType, to render a lower and an upper bound [Form] Add BetweenType to render a lower and an upper bound Sep 9, 2026
@seb-jean
seb-jean force-pushed the feat/form-between-type branch 2 times, most recently from dc97078 to 5fb9fe5 Compare September 9, 2026 19:50
@seb-jean

seb-jean commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Done. The PR is back to a dedicated BetweenType and rebased on 8.2. Translations are out of it, as suggested.

The naming question is still open: BetweenType, or BoundariesType as @alexandre-daubois suggested? I have no strong preference, happy to rename.

@yceruto yceruto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, this would removes the mechanical boilerplate of defining two fields.

My main concern is that the more important part "validating their ordering" is still left to the caller. The tests show this clearly, with the same Callback repeated to enforce from <= to.

I think this new type would be much more useful if it provides an optional ordering check out of the box, with a callable for types it cannot compare directly:

$builder->add('price', BetweenType::class, [
    'type' => MoneyType::class,
    'compare' => true, // or a callable for inner types the component cannot order
]);

This would remove the boilerplate that is easier to get wrong and make the type more valuable as a core abstraction.

Comment thread src/Symfony/Component/Form/Extension/Core/Type/BetweenType.php Outdated
Comment thread src/Symfony/Component/Form/Extension/Core/Type/BetweenType.php Outdated
Comment thread src/Symfony/Component/Form/Extension/Core/Type/BetweenType.php Outdated
Comment thread src/Symfony/Component/Form/Extension/Core/Type/BetweenType.php Outdated
Comment thread src/Symfony/Component/Form/Extension/Core/Type/BetweenType.php Outdated
@seb-jean
seb-jean force-pushed the feat/form-between-type branch from 5fb9fe5 to bf0d191 Compare September 11, 2026 12:52
@seb-jean seb-jean changed the title [Form] Add BetweenType to render a lower and an upper bound [Form] Add BoundsType to render a lower and an upper bound Sep 11, 2026
@seb-jean
seb-jean force-pushed the feat/form-between-type branch 5 times, most recently from aa744f1 to f78ccf3 Compare September 11, 2026 14:51
Comment thread src/Symfony/Component/Form/Extension/Core/Type/BoundsType.php Outdated
Comment thread src/Symfony/Component/Form/Extension/Core/Type/BoundsType.php Outdated
@seb-jean
seb-jean force-pushed the feat/form-between-type branch from f78ccf3 to c5861d8 Compare September 12, 2026 16:29
@seb-jean
seb-jean force-pushed the feat/form-between-type branch from c5861d8 to 2d34187 Compare September 12, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Form] Add a type for a from/to pair

5 participants