Skip to content

Instantly share code, notes, and snippets.

@sstok
Last active October 30, 2023 19:55
Show Gist options
  • Save sstok/d0b63f4ba07834e8199849d0d1ca3d2f to your computer and use it in GitHub Desktop.
Save sstok/d0b63f4ba07834e8199849d0d1ca3d2f to your computer and use it in GitHub Desktop.
Form view ordering

There was a discussion about the ordering for the Forms (view level). symfony/symfony#5827 which unfortunately ended without a consensus.

You can handle this in the template itself, but that only works if you control the template that is used. Which is not always the case, FormTypeExtensions adding additional forms and theming like the SyliusThemeBundle provides.

Which means that whenever you add additional forms or change the theme you need to update this at multiple places.

One of the suggested alternatives was to use a before/after option, which provides enough flexibility for this use-case.

And now my proposal:

$builder->add('name', FormType::class, ['position' => ['before', 'form-name']]);

Which in the finishView() method is properly re-ordered for display.

The position is a nested OptionResolver with at, 'name', and 'priority' as options. This merely a simplified usage with a normalizer.

Internally the following algorithm is used for re-sorting.

NOTE: This is a proof of concept, hasn't been worked-on further.

<?php
declare(strict_types=1);
$forms = [
'first-name' => [
'position' => null
],
'last-name' => [
'position' => null
],
'sur-name' => [
'position' => ['before', 'last-name']
],
'birthday' => [
'position' => null
],
'status' => [
'position' => ['after', 'sur-name']
],
];
$visited = [];
$final = [];
// First build new sorting order
foreach ($forms as $name => $config) {
$visited[$name] = ['before' => [], 'after' => [], 'config' => $config];
if ($config['position'] !== null) {
if (!isset($visited[$config['position'][1]])) {
throw new \InvalidArgumentException(sprintf('Form %s must be defined before processing the form %s.', $config['position'][1], $name));
}
$visited[$config['position'][1]][$config['position'][0]][] = $name;
$visited[$name]['resorted'] = true;
}
}
// Then rebuild the form list
function processForm(array $children, array $visited, array &$final, string $name): void
{
foreach ($children['before'] as $childName) {
processForm($visited[$childName], $visited, $final, $childName);
}
$final[$name] = $visited[$name]['config'];
foreach ($children['after'] as $childName) {
processForm($visited[$childName], $visited, $final, $childName);
}
}
foreach ($visited as $name => $children) {
if (isset($children['resorted'])) {
continue;
}
processForm($children, $visited, $final, $name);
}
assert($final ===
[
'first-name' => [
'position' => null,
],
'sur-name' => [
'position' => ['before', 'last-name'],
],
'status' => [
'position' => ['after', 'sur-name'],
],
'last-name' => [
'position' => null,
],
'birthday' => [
'position' => null,
],
]
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment