Problem/Motivation
New deprecation in Symfony 7.3 via #3522353: Update Composer dependencies for 11.2.0
Since symfony/validator 7.3: Passing an array of options to configure the "..." constraint is deprecated, use named arguments instead.
Steps to reproduce
Proposed resolution
Use named arguments instead for Symfony constraints.
In follow ups:
Add named argument support and a deprecation/BC layer for options arrays to custom Drupal constraints.
Remaining tasks
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
Issue fork drupal-3522497
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3522497-deprecation-poc
changes, plain diff MR !12291
- 3522497-passing-a-options
changes, plain diff MR !12268
Comments
Comment #2
godotislateNote from @longwave about this on Slack:
I don't think reflection on the class will have much performance penalty, since the plugin class is being instantiated right after anyway, so it's getting loaded in memory regardless.
But if it does turn out to be a performance issue, one idea I have is to add functionality toAttributeClassDiscovery::parseClass()to be able to pick up additional attributes like this and add them to the plugin definition, since the plugin class is already being reflected in discovery. The effort in #3443882: Allow attribute-based plugins to discover supplemental attributes to set additional properties could be amended to include this.It would look something like:InparseClass(), call a new method, something likeparseAdditionalAttributes()that takes the ReflectionClass object as one of its parametersInparseAdditionalAttributes(), check whether the plugin class implements a (new) Interface, maybe something likeAdditionalAttributesPluginInterface(name TBD), that has its ownpublic static function parseAdditionalAttributes()methodPlugin classes implementing the interface and method can then use the reflection class object to look at other attributes on the class or its methods, properties, etc. specific to the plugin classFor relevant constraint plugins, the implementation ofparseAdditionalAttributes()would look at the constructor for the theHasNamedArgumentsattribute, and if so, that gets added back to the plugin definitionNote that the above would have to account for attributes possibly being defined in other modules and the attribute discovery filecache, so there are complications.And I'm not completely sure this would be worth the effort for this use case, but just throwing it out there as a possibility.Never mind, obviously additional interfaces can't be added Symfony classes, and several of the Symfony constraint plugins are added directly in the plugin manager and not through attribute discovery.
Comment #3
longwaveMaking this active and major, as this will likely require new deprecations and will be removed by Symfony 8/Drupal 12, we should aim to land this in 11.3.
Comment #5
godotislateRemoved the entry in .deprecation-ignore.txt and finally got all tests passing.
The deprecation layer still needs doing. It might be a bit complex and probably needs some thinking through. Among other things, even though all the docblocks for
addConstraint($constraint_name, $options = NULL)have this type for the options@param array|null $options, there are cases such as this inEntityDataDefinition::setEntityType()where$optionsis a string:The
ConstraintManagereven accounts for this by turning non-array $options into array in::create():Problem is that with named arguments, an exception is thrown with invalid argument names, and it can't be assumed that
valuewill always be a valid argument name.Comment #7
godotislatePut up a new separate MR 12291 to explore deprecating an array options to Drupal constraints. To take a bit of shortcut in not having to add constructors to every Drupal constraint plugin that doesn't have one, (or at least to put off doing them all now), I created a new base constraint class that looks like this:
Then I set all Drupal constraints that were directly extending
Symfony\Component\Validator\Constraintto extend this base class instead.In addition, I deprecated passing string or list
$optionsto the variousaddConstraint($name, $options)methods. Generally people won't be calling constraint plugin constructors directly, and will be using addConstraint to pass the options, and this is the cleanest way to make sure that the $options passed line up with constraint plugin class properties.That all said, doing all this seems pretty cumbersome. As is it's not really taking advantage of typed values you get named constructor parameters, since right now there's just a lot of trickery leveraging the
...operator.It might be possible to continue to allow string and list arrays to be passed to
addConstraint(), and removes a lot of the need to add keys to all the arrays, but if and when the constraint classes actually have named parameters in their constructors, that might not work anymore.Anyway, as mentioned this additional work is done in a separate MR (12291), and MR 12268 is the one that handles only deprecation warnings from the Symfony constraints.
Comment #8
godotislateOne more thought: with named parameters, does this start to make constraint plugin classes or constructors API?
Comment #10
longwaveRun into this again via #3554533: Update to Symfony 7.4.0-BETA2
Do we need to worry about issuing our own deprecation here, or can we just rely on the Symfony one if we go the MR!12268 route?
Comment #11
godotislateRe #10. Discussed with @longwave on Slack, and best way forward here to get this in for 11.3 is to reduce scope to address only the Symfony constraints for now (MR 12268). Can split off using named parameters on all constraints to a follow up.
We should also try to include our own deprecation message somehow as well, since most people using constraints do not instantiate directly via the constructor but through
addConstraint(), so the message telling them to use named parameters instead is not that helpful.I will try to work on 12268 either today or tomorrow.
Comment #12
godotislateComment #14
godotislateUpdated MR 12268 with a small refactoring. Still WIP for some tests of the logic in ConstraintFactory, and this needs a CR.
Comment #15
longwaveMade a start on a change record: https://www.drupal.org/node/3554746
Comment #16
longwaveStarted looking at tests, but wondering if we should split this out into more issues. If we can remove the deprecation skip here and do the minimum amount of work in ConstraintFactory - perhaps not even issue the deprecation - then that unblocks the start of the Symfony 7.4 upgrade. Symfony 7.4 then adds some new deprecations, so in a followup we try to upgrade to
symfony/validator7.4, fix the new deprecations, and start issuing our own deprecations?I also wonder if we want to do something similar to ConstraintManager's $options argument in a followup, or if we shouldn't bother.
Comment #17
godotislateRe #16, I think reducing scope here to minimum to get tests passing with the Symfony deprecation ignore removed makes sense, and handle everything else in follow ups. I'll do my best to get that done today.
Comment #18
godotislateMade changes per MR feedback. Removed the deprecation warning as well.
One thing I wonder if we need to do here: in
ConstraintManager::create(), there's code to convert$optionsnot passed as an array to an array with avaluekey. This worked because when passed as an $options array to the plugin constructor, if the plugin classes returns something fromgetDefaultOption(), the value at thevaluekey gets set as the value for the default option.This doesn't necessarily work with named arguments, because
$valuemight not be a constructor argument. I did some handling for this in MR 12291, by adding an internal boolean flag value to the $options array to indicate that options were not originally passed as an array, so that they shouldn't be passed as named arguments. Nothing is breaking in core tests, but I wonder if we need this possibly for contrib/custom code use of the Symfony Constraint subclasses. Or maybe it's fine to leave to a follow up?Do we need any tests here now?
Comment #19
longwave> there's code to convert
$optionsnot passed as an array to an array with avaluekeyI think in a followup we should deprecate this behaviour, and enforce that an array must be passed, given that in D12/SF8 we will only be able to use named arguments anyway. I am not sure
valueis a sensible default here anyway, it should use the result ofgetDefaultOption()surely?For me we don't need additional tests here as this is all necessary work to get the deprecation skip removed. The followups might need some when we start issuing our own deprecations as there will be multiple subtly different code paths there.
Bumping to critical as this is blocking SF7.4.
Comment #20
godotislateComment #21
godotislateMade minor tweaks the IS and issue title. Might also need a plan on how to break up follow up work, if it needs to be broken up at all.
Comment #23
godotislateStubbed a follow up #3555134: Deprecate passing options array to all constraint plugin parameters. Re #21 about a plan, maybe it can look something like this:
First, carry over most if not all the work from MR 12291, including
addConstraint()methodsThen later, in Drupal 13(?):
Comment #27
larowlanCommitted to 11.x and backported to 11.3.x
Thanks!
Comment #29
larowlanPublished the change record
Comment #30
godotislateSince we didn't do any deprecation here, linked https://www.drupal.org/node/3554746 to #3555134: Deprecate passing options array to all constraint plugin parameters instead.
Comment #31
kim.pepperNot sure if we need to do anything with
\Drupal\file\Validation\Constraint\UploadedFileConstraint?Comment #35
alexpottDid we come to any conclusion about what do for
Comment #37
codebymikey commentedThis change currently breaks backwards compatibility with the Regex constraint, and throws this fatal exception when inspecting configurations using constraints like:
This is because the Regex constraint uses
$parameteras its first argument rather than$value, so special handling might be necessary for it (as well as any other non-standard constraint)Not sure whether it should be addressed here, or opened as a separate issue.
Comment #38
alorenc@codebymikey, I noticed the same issue, and I had to apply a few patches. This depended on the project’s modules; however, in my case, it was addressed by the following patch: https://www.drupal.org/i/3567470, https://www.drupal.org/i/3567470