Skip to content

[Form] choices ids are malformed #1919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from

Conversation

brikou
Copy link
Contributor

@brikou brikou commented Aug 7, 2011

Choices ids are not malformed, they should be normalized (slugged)

When using this...

<?php

$builder
    ->add('color', 'choice', array(
        'choices' => array(
            ($k = 'The color #1') => $k,
            ($k = 'The color #2') => $k,
            ($k = 'The color #3') => $k,
        ),
        'expanded' => true,
    ))
;

I get this...

<div id="form_color">
    <input type="radio" id="form_color_The color #1" name="form[color]" required="required" value="The color #1">
    <label for="form_color_The color #1" class="required">The color #1</label>

    <input type="radio" id="form_color_The color #2" name="form[color]" required="required" value="The color #2">
    <label for="form_color_The color #2" class="required">The color #2</label>

    <input type="radio" id="form_color_The color #3" name="form[color]" required="required" value="The color #3">
    <label for="form_color_The color #3" class="required">The color #3</label>
</div>

Instead of this...

<div id="form_color">
    <input type="radio" id="form_color_the-color-1" name="form[color]" required="required" value="The color #1">
    <label for="form_color_the-color-1" class="required">The color #1</label>

    <input type="radio" id="form_color_the-color-2" name="form[color]" required="required" value="The color #2">
    <label for="form_color_the-color-2" class="required">The color #2</label>

    <input type="radio" id="form_color_the-color-3" name="form[color]" required="required" value="The color #3">
    <label for="form_color_the-color-3" class="required">The color #3</label>
</div>

NB: Things would be easier to implement/test with a new standalone Inflector Component (with dasheize, pluralize, humanize ... functions)

@phidah
Copy link
Contributor

phidah commented Aug 9, 2011

Good idea, however it should be made sure that the same ID is not generated twice if the "robotization" of the names generate the same string.

@brikou
Copy link
Contributor Author

brikou commented Aug 9, 2011

thanks @phidah, I think that duplication of IDs should be prevented by coder himself when creating the form

@phidah
Copy link
Contributor

phidah commented Aug 9, 2011

I disagree, since the coder has no control of the "robotification" of the name.
Instead something should probably be appended in order to make them unique (such as "-1", "-2", etc.).

@brikou
Copy link
Contributor Author

brikou commented Aug 9, 2011

IMO implementing this kind of prevention is quite hard to do, and anyway if there is any duplication of id, it doesn't break the behaviour of the form.
For now I'd like to have feedback for the main pruporse of this PR ;)

@stof
Copy link
Member

stof commented Aug 9, 2011

@brikou duplicated ids break the behavior of the form. Clicking on the label checks the radio input, but if the id is duplicated, it will either check both or a random one among them.

@@ -71,6 +71,7 @@ class FieldType extends AbstractType
public function buildView(FormView $view, FormInterface $form)
{
$name = $form->getName();
$name = $this->robotize($name);
Copy link
Contributor

Choose a reason for hiding this comment

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

why not $name = $this->robotize($form->getName());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@brikou
Copy link
Contributor Author

brikou commented Aug 9, 2011

@stof I meant it doesn't break the form submission behaviour (on the server side)... but having the same ids would mean that some labels would be the same (omitting special char like # ! ?)
This PR is not perfect but better than the actual behaviour of id generation.

@henrikbjorn
Copy link
Contributor

I know lithium have a inflector class, maybe we can borrow it

@stof
Copy link
Member

stof commented Aug 9, 2011

@brikou The form submission would not be broken but the usability of the form would be. So it is an issue.

@brikou
Copy link
Contributor Author

brikou commented Aug 9, 2011

@stof I finally agree (i finally thought about a problematic use case) so I guess a first loop over choices is required, isn't it... I also think we should write a test , but can you help me writing this test?

@brikou
Copy link
Contributor Author

brikou commented Aug 9, 2011

So this patch works with:

<?php

$builder
    ->add('color', 'choice', array(
        'choices' => array(
            ($k = 'The color #1') => $k,
            ($k = 'The color #2') => $k,
            ($k = 'The color #3') => $k,
        ),
        'expanded' => true,
    ))
;

but not with this (because dasheized names leads to not unique id):

<?php

$builder
    ->add('color', 'choice', array(
        'choices' => array(
            ($k = 'The color ???') => $k,
            ($k = 'The color !!!') => $k,
            ($k = 'The color $$$') => $k,
        ),
        'expanded' => true,
    ))
;

if anyone has a clue (@stof or even @fabpot ;)) on how to loop over sibling radio in order to make sure generated id is unique it would be great ;)

@henrikbjorn
Copy link
Contributor

well we could just suffix them with uniqid() or something

@phidah
Copy link
Contributor

phidah commented Aug 9, 2011

Woundn't be very pretty but would work.

@brikou
Copy link
Contributor Author

brikou commented Aug 9, 2011

Ok now everything works without duplicated ids... (works event with multiple set to true) @stof can you give me your feedback

$clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
$clean = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $clean);
$clean = strtolower(trim($clean, '-'));
$clean = preg_replace("/[\/_|+ -]+/", $delimiter, $clean);
Copy link
Member

Choose a reason for hiding this comment

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

why removing the underscores ? they are valid AFAIK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just to keep a single delimiter

Copy link
Member

Choose a reason for hiding this comment

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

but this is what force you to use a hack for the CSRF token field. And the form framework already uses underscores as delimiter in many places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I know that underscore is used a lot but with the use of the dash as a delimiter, it emphasizes the hierarchy in the form's ids wich is usefull when writing js for example

$('#post_author-name'); // with dash, i can say that this selector is related to post's author_name field
$('#post_author_name'); // with underscore, ... I don't know what is the relation

This is a silly example but I'm sure you understand what I'm trying to explain

@fabpot
Copy link
Member

fabpot commented Aug 23, 2011

Apart form my comments, I don't really like this patch. I understand the problem but it makes things look really complicated.

@brikou
Copy link
Contributor Author

brikou commented Aug 23, 2011

I'm glad you understand this problem, I think a great thing would be to have a shared inflector class, which could be use by every component when needed (in form view to humanize label, dasheize id, by twig to generate class from "category" for instance, by doctrine to pluralize, camelize, tableize etc...).

@fabpot Should I close this PR or keep it open (to keep the emphase of this matter) and wait for a better implementation?

@fabpot
Copy link
Member

fabpot commented Aug 23, 2011

Keep it open.

@stof
Copy link
Member

stof commented Aug 23, 2011

@brikou having a shared one between the third-party libraries used by Symfony seems weird. Each of these library should not depend on Symfony2. Thus, Doctrine already has a tool to do this for their need, and for Twig, I don't see what you mean by generate class from "category"

webmozart added a commit to webmozart/symfony that referenced this pull request Jan 23, 2012
webmozart added a commit to webmozart/symfony that referenced this pull request Jan 23, 2012
…tion of HTML IDs and to (2) avoid problems with property paths.

ad (1): HTML4 "id" attributes are limited to strings starting with a letter and containing only letters, digits, underscores, hyphens, periods and colons.

ad (2): Property paths contain three special characters needed for correct parsing: left/right bracket and period.

The rules for form naming are:

* Names may start with a letter, a digit or an underscore. Leading digits or underscores will be stripped from the "id" attributes.
* Names must only contain letters, digits, underscores, hyphens and colons.
* Root forms may have an empty name.

Solves symfony#1919 and symfony#3021 on a wider scope.
fabpot added a commit that referenced this pull request Jan 28, 2012
Commits
-------

8dc78bd [Form] Fixed YODA issues
600cec7 [Form] Added missing entries to CHANGELOG and UPGRADE
b154f7c [Form] Fixed docblock and unneeded use statement
399af27 [Form] Implemented checks to assert that values and indices generated in choice lists match their requirements
5f6f75c [Form] Fixed outstanding issues mentioned in the PR
7c70976 [Form] Fixed text in UPGRADE file
c26b47a [Form] Made query parameter name generated by ORMQueryBuilderLoader unique
18f92cd [Form] Fixed double choice fixing
f533ef0 [Form] Added ChoiceView class for passing choice-related data to the view
d72900e [Form] Incorporated changes suggested in PR comments
28d2f6d Removed duplicated lines from UPGRADE file
e1fc5a5 [Form] Restricted form names to specific characters to (1) fix generation of HTML IDs and to (2) avoid problems with property paths.
87b16e7 [Form] Greatly improved ChoiceListInterface and all of its implementations

Discussion
----------

[Form] Improved ChoiceList implementation and made form naming more restrictive

Bug fix: yes
Feature addition: yes
Backwards compatibility break: **yes**
Symfony2 tests pass: yes
Fixes the following tickets: #2869, #3021, #1919, #3153
Todo: adapt documentation

![Travis Build Status](https://secure.travis-ci.org/bschussek/symfony.png?branch=issue1919)

The changes in this PR are primarily motivated by the fact that invalid form/field names lead to various problems.

1. When a name contains any characters that are not permitted in HTML "id" attributes, these are invalid
2. When a name contains periods ("."), form validation is broken, because they confuse the property path resolution
3. Since choices in expanded choice fields are directly translated to field names, choices applying to either 1. or 2. lead to problems. But choices should be unrestricted.
4. Unless a choice field is not expanded and does not allow multiple selection, it is not possible to use empty strings as choices, which might be desirable in some occasions.

The solution to these problems is to

* Restrict form names to disallow unpermitted characters (solves 1. and 2.)
* Generate integer indices to be stored in the HTML "id" and "name" attributes and map them to the choices (solves 3.). Can be reverted to the old behaviour by setting the option "index_generation" to ChoiceList::COPY_CHOICE
* Generate integer values to be stored in the HTML "value" attribute and map them to the choices (solves 4.). Can be reverted to the old behaviour by setting the option "value_generation" to ChoiceList::COPY_CHOICE

Apart from these fixes, it is now possible to write more flexible choice lists. One of these is `ObjectChoiceList`, which allows to use objects as choices and is bundled in the core. `EntityChoiceList` has been made an extension of this class.

    $form = $this->createFormBuilder()
        ->add('object', 'choice', array(
            'choice_list' => new ObjectChoiceList(
                array($obj1, $obj2, $obj3, $obj4),
                // property path determining the choice label (optional)
                'name',
                // preferred choices (optional)
                array($obj2, $obj3),
                // property path for object grouping (optional)
                'category',
                // property path for value generation (optional)
                'id',
                // property path for index generation (optional)
                'id'
            )
        ))
        ->getForm()
    ;

---------------------------------------------------------------------------

by kriswallsmith at 2012-01-19T18:09:09Z

Rather than passing `choices` and a `choice_labels` arrays to the view would it make sense to introduce a `ChoiceView` class and pass one array of objects?

---------------------------------------------------------------------------

by stof at 2012-01-22T15:32:36Z

@bschussek can you update your PR according to the feedback (and rebase it as it conflicts according to github) ?

---------------------------------------------------------------------------

by bschussek at 2012-01-24T00:15:42Z

@kriswallsmith fixed

Fixed all outstanding issues. Would be glad if someone could review again, otherwise this PR is ready to merge.

---------------------------------------------------------------------------

by fabpot at 2012-01-25T15:17:59Z

Is it ready to be merged?

---------------------------------------------------------------------------

by Tobion at 2012-01-25T15:35:50Z

Yes I think so. He said it's ready to be merged when reviewed.

---------------------------------------------------------------------------

by bschussek at 2012-01-26T02:30:36Z

Yes.

---------------------------------------------------------------------------

by bschussek at 2012-01-28T12:39:00Z

Fixed outstanding issues. Ready for merge.
@fabpot fabpot closed this Jan 28, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants