17
17
use Symfony \Component \HttpKernel \Exception \UnprocessableEntityHttpException ;
18
18
use Symfony \UX \LiveComponent \Attribute \BeforeReRender ;
19
19
use Symfony \UX \LiveComponent \Attribute \LiveProp ;
20
+ use Symfony \UX \LiveComponent \Util \LiveFormUtility ;
21
+ use Symfony \UX \TwigComponent \Attribute \PostMount ;
20
22
21
23
/**
22
24
* @author Ryan Weaver <[email protected] >
@@ -63,17 +65,29 @@ trait ComponentWithFormTrait
63
65
*/
64
66
abstract protected function instantiateForm (): FormInterface ;
65
67
66
- /**
67
- * Override in your class if you need extra mounted values.
68
- *
69
- * Call $this->setForm($form) manually in that situation
70
- * if you're passing in an initial form.
71
- */
72
- public function mount (?FormView $ form = null )
68
+ #[PostMount]
69
+ public function postMount (array $ data ): array
73
70
{
74
- if ($ form ) {
75
- $ this ->setForm ($ form );
71
+ // allow the FormView object to be passed into the component() as "form"
72
+ if (\array_key_exists ('form ' , $ data )) {
73
+ $ this ->formView = $ data ['form ' ];
74
+ unset($ data ['form ' ]);
75
+
76
+ if ($ this ->formView ) {
77
+ // if a FormView is passed in and it contains any errors, then
78
+ // we mark that this entire component has been validated so that
79
+ // all validation errors continue showing on re-render
80
+ if (LiveFormUtility::doesFormContainAnyErrors ($ this ->formView )) {
81
+ $ this ->isValidated = true ;
82
+ $ this ->validatedFields = [];
83
+ }
84
+ }
76
85
}
86
+
87
+ // set the formValues from the initial form view's data
88
+ $ this ->initializeFormValues ();
89
+
90
+ return $ data ;
77
91
}
78
92
79
93
/**
@@ -87,7 +101,7 @@ public function mount(?FormView $form = null)
87
101
public function submitFormOnRender (): void
88
102
{
89
103
if (!$ this ->getFormInstance ()->isSubmitted ()) {
90
- $ this ->submitForm (false );
104
+ $ this ->submitForm ($ this -> isValidated );
91
105
}
92
106
}
93
107
@@ -103,18 +117,6 @@ public function getForm(): FormView
103
117
return $ this ->formView ;
104
118
}
105
119
106
- /**
107
- * Call this from mount() if your component receives a FormView.
108
- *
109
- * If your are not passing a FormView into your component, you
110
- * don't need to call this directly: the form will be set for
111
- * you from your instantiateForm() method.
112
- */
113
- public function setForm (FormView $ form ): void
114
- {
115
- $ this ->formView = $ form ;
116
- }
117
-
118
120
public function getFormName (): string
119
121
{
120
122
if (!$ this ->formName ) {
@@ -124,18 +126,19 @@ public function getFormName(): string
124
126
return $ this ->formName ;
125
127
}
126
128
127
- public function getFormValues (): array
129
+ private function initializeFormValues (): void
128
130
{
129
- if (null === $ this ->formValues ) {
130
- $ this ->formValues = $ this ->extractFormValues ($ this ->getForm ());
131
- }
132
-
133
- return $ this ->formValues ;
131
+ $ this ->formValues = $ this ->extractFormValues ($ this ->getForm ());
134
132
}
135
133
136
134
private function submitForm (bool $ validateAll = true ): void
137
135
{
138
- $ this ->getFormInstance ()->submit ($ this ->formValues );
136
+ if (null !== $ this ->formView ) {
137
+ throw new \LogicException ('The submitForm() method is being called, but the FormView has already been built. Are you calling $this->getForm() - which creates the FormView - before submitting the form? ' );
138
+ }
139
+
140
+ $ form = $ this ->getFormInstance ();
141
+ $ form ->submit ($ this ->formValues );
139
142
140
143
if ($ validateAll ) {
141
144
// mark the entire component as validated
@@ -146,10 +149,19 @@ private function submitForm(bool $validateAll = true): void
146
149
// we only want to validate fields in validatedFields
147
150
// but really, everything is validated at this point, which
148
151
// means we need to clear validation on non-matching fields
149
- $ this ->clearErrorsForNonValidatedFields ($ this -> getFormInstance () , $ this -> getFormName ());
152
+ $ this ->clearErrorsForNonValidatedFields ($ form , $ form -> getName ());
150
153
}
151
154
152
- if (!$ this ->getFormInstance ()->isValid ()) {
155
+ // re-extract the "view" values in case the submitted data
156
+ // changed the underlying data or structure of the form
157
+ $ this ->formValues = $ this ->extractFormValues ($ this ->getForm ());
158
+ // remove any validatedFields that do not exist in data anymore
159
+ $ this ->validatedFields = LiveFormUtility::removePathsNotInData (
160
+ $ this ->validatedFields ?? [],
161
+ [$ form ->getName () => $ this ->formValues ],
162
+ );
163
+
164
+ if (!$ form ->isValid ()) {
153
165
throw new UnprocessableEntityHttpException ('Form validation failed in component ' );
154
166
}
155
167
}
@@ -192,7 +204,7 @@ private function getFormInstance(): FormInterface
192
204
return $ this ->formInstance ;
193
205
}
194
206
195
- private function clearErrorsForNonValidatedFields (Form $ form , $ currentPath = '' ): void
207
+ private function clearErrorsForNonValidatedFields (Form $ form , string $ currentPath = '' ): void
196
208
{
197
209
if (!$ currentPath || !\in_array ($ currentPath , $ this ->validatedFields , true )) {
198
210
$ form ->clearErrors ();
0 commit comments