-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
Conversation
* | ||
* @param $class | ||
* @param $property | ||
* @return bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boolean
do you think a note in the documentation should be made about not using |
You can still use 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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Changes done... Ping @bschussek |
* | ||
* @return string[] List of supported type mappings | ||
*/ | ||
protected function getSupportedButtonTypeMappings() |
There was a problem hiding this comment.
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',
);
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 TypeGuesser
s to use it) and make array private static as asked.
… specialized button type based on the field name and the underlying data_class
@stof better? |
Ping @bschussek |
|
||
return false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run php-cs-fixer
#10570 has been merged now. |
* | ||
* @return Boolean | ||
*/ | ||
public static function hasPropertyAvailable($class, $property) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
#10570 requires an object to verify if the property is readable/writable or not.
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) { |
There was a problem hiding this comment.
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).
@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 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. |
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! |
* The old behavior would of caused an exception to be thrown while binding the data.
Created during the Symfony Montreal Hackathon