Skip to content

[Form] Implemented CoreTypeGuesser for submit button detection #9592

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 1 commit into from
Closed

[Form] Implemented CoreTypeGuesser for submit button detection #9592

wants to merge 1 commit into from

Conversation

FineWolf
Copy link
Contributor

Q A
Bug fix? no
New feature? yes
BC breaks? no*
Deprecations? no
Tests pass? yes
Fixed tickets #8785
License MIT
Doc PR N/A

* The old behavior would of caused an exception to be thrown while binding the data.

Created during the Symfony Montreal Hackathon

*
* @param $class
* @param $property
* @return bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Boolean

@cordoval
Copy link
Contributor

do you think a note in the documentation should be made about not using submit or reset as keywords for properties added to a formtype?

@FineWolf
Copy link
Contributor Author

You can still use submit and reset as names for properties. This only infers the type if no type has been specified and if there is no such property in the data_class. It doesn't make submit and reset reserved keywords.

Worst comes to worst, you have to specify the type.

Ping @bschussek

$adderFound = $this->isMethodAccessible($reflClass, $adder, 1);
$removerFound = $this->isMethodAccessible($reflClass, $remover, 1);

return $adderFound && $removerFound;
Copy link
Member

Choose a reason for hiding this comment

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

This logic will not work. An object being traversable does not mean it will always be handled through the adder and remover. This is even a very uncommon case (the only way I see to do it is to attach the collection type on the getIterator method). The adder and remover are on the parent object, not on the collection itself

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Logic modified. I had read PropertyAccessor wrong. If there is an adder and a remover however, we want to assume submit is there.

@FineWolf
Copy link
Contributor Author

Changes done... Ping @bschussek

*
* @return string[] List of supported type mappings
*/
protected function getSupportedButtonTypeMappings()
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO this should be moved to a static property:

private static $nameToTypeMapping = array(
    'submit' => 'submit',
    'reset' => 'reset',
);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bschussek: This would prevent someone from modifying the list of supported mappings thru inheritance.

Consider this scenario: Someone creates their own button type (let's call it random for example sakes) and they want to allow $builder->add('random')... The way it is now, they can easily override getSupportedButtonTypeMappings() in a way that is agnostic to future modifications of the mappings as such:

/**
 * {@inheritDoc}
 */
protected function getSupportedButtonTypeMappings()
{
    $mappings = parent::getSupportedButtonTypeMappings();
    $mappings['random'] = 'random';
    return $mappings;
}

and then change the %form.type_guesser.core.class% parameter to their overloaded class.

By making it a private static variable, the developer in the scenario above would have to redo all the work.

Copy link
Member

Choose a reason for hiding this comment

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

If they provide their own type, they should be providing a separate guesser, not replacing the guesser of the core extension

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'm sorry @stof but I strongly disagree. The whole point of having an object-oriented service container is to be able to replace specific services with your own implementation that extends the base implementation if you have a very small change to make. In fact, this is one of the main advantages of dependency injection coupled with the service locator pattern.

The use case above is a perfectly valid use case that happens all the time in software development. Forcing the user to copy-paste code into its own implementation to simply add what arguably should be a configurable element is, IMHO, an idiotic design decision. Ping @fabpot

In fact, this is something I'm doing in my own projects. For example, I extended the default ProfilerListener to only save profiling information of non-404 exceptions when only_exceptions is enabled in the configuration. If onKernelException would not of been overridable and the profiler_listener.class parameter would not be available, I would of had to roll out my own complete ProfilerListener implementation. That would of been costly and not at all elegant. Plus, this approach would of required me to update it manually when the Symfony version updates. That would of been utterly ridiculous.

Copy link
Member

Choose a reason for hiding this comment

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

@FineWolf I'm not talking about the container here. I'm talking about the way the Form component works. Extending the core guesser to replace it can only be done by 1 package wanting to provide a new button type (the next one would need to be aware of the previous overwrite, which is impossible for shared stuff). The Form component lets many different guessers being registered, so registering a separate guesser is a fat better way to extend the form component than replacing the gusser of another extension.
Composition is most of the time a far better extension point than inheritance.

Your argument for making the class extendable is that other guesser may need to reuse the other protected properties to inspect the class. My own conclusion based on the same need is that these other protected methods should not be part of this type guesser but be in a collaborator instead (which can then be reused by other guessers)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stof The point is not to make it extendable for multiple packages, but on a special case basis. Of course if you are building a reusable bundle you would want to roll out your own TypeGuesser, however there are special cases where for when you are building that one specific project in that one specific case, you might want to add a new supported button type, or maybe disable the support for reset and keep the one for submit.

Both are totally valid points. However I do not think we must be overly restrictive in this case. There are still valid use-cases for having a protected function here.

In fact, having it protected here does allow someone to register their implementation as a new TypeGuesser, keeping the logic of the original one and simply returning a new supported type.

At the end of the day, I'll go with whatever you guys decide. Just make an informed decision.

Copy link
Member

Choose a reason for hiding this comment

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

The issue if we make it protected is that we have to maintain BC on it for the full 2.x lifetime (because changing a protected method is a BC rbeak fo people extending the class). This is why we use private everywhere unless we explicitly want to make it a supported extension point (and before switching a method to protected to add an extension point, we think about other ways to achieve the extension to decide whether switching to protected is the best way to provide it. As I said at the end of my command above, there might be better ways to achieve the use case)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stof I'll move the property detection logic in a separate class (which would allow other TypeGuessers to use it) and make array private static as asked.

… specialized button type based on the field name and the underlying data_class
@FineWolf
Copy link
Contributor Author

@stof better?

@FineWolf
Copy link
Contributor Author

Ping @bschussek


return false;
}
}
Copy link

Choose a reason for hiding this comment

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

run php-cs-fixer

@webmozart
Copy link
Contributor

Thanks @FineWolf for working on this! This PR should be adapted as soon as #10570 is merged. Then we can merge this one too.

@fabpot
Copy link
Member

fabpot commented Mar 31, 2014

#10570 has been merged now.

*
* @return Boolean
*/
public static function hasPropertyAvailable($class, $property)
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like much code duplication with the PropertyAccess component. We can probably reuse it which is a required dependency anyway. Maybe the new isReadable method, see #10570

Copy link
Member

Choose a reason for hiding this comment

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

@Tobion you should have looked at the discussion first. The PR was already referenced

@FineWolf
Copy link
Contributor Author

#10570 requires an object to verify if the property is readable/writable or not.

Symfony\Component\Form\FormTypeGuesserInterface::guessType passes the class name as a string, not the actual data object.

Other than creating an instance of the data class (which we might not be able to due to constructor signature), I fail to see how #10570 will help us here.

Ping @webmozart

{
return preg_replace_callback(
'/(^|_|\.)+(.)/',
function ($match) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this a regular method? As your creating a closure every time the camelize method is called (which can be quite allot).

@webmozart
Copy link
Contributor

@FineWolf Sorry for the late feedback!

I'd really like to move the inspection features into the PropertyAccess component, otherwise we risk having two incompatible or at least different implementations here and there. I think it would be sufficient if isReadable() and isWritable() supported class names if the property path is one element long.

There is one more problem (and that's the same for other guessers) which is that the "property_path" option is ignored, if set. This can probably be solved in the scope of #7868.

@webmozart
Copy link
Contributor

I'm closing this since the PR is really rather large for replacing

$form->add('submit', 'submit');

by

$form->add('submit');

I want to prevent changing/adding big amounts of code if not really necessary. Thanks again @FineWolf!

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.

9 participants