1717use Symfony \Component \HttpKernel \Exception \UnprocessableEntityHttpException ;
1818use Symfony \UX \LiveComponent \Attribute \BeforeReRender ;
1919use Symfony \UX \LiveComponent \Attribute \LiveProp ;
20+ use Symfony \UX \LiveComponent \Util \LiveFormUtility ;
21+ use Symfony \UX \TwigComponent \Attribute \PostMount ;
2022
2123/**
2224 * @author Ryan Weaver <[email protected] > @@ -63,17 +65,29 @@ trait ComponentWithFormTrait
6365 */
6466 abstract protected function instantiateForm (): FormInterface ;
6567
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
7370 {
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+ }
7685 }
86+
87+ // set the formValues from the initial form view's data
88+ $ this ->initializeFormValues ();
89+
90+ return $ data ;
7791 }
7892
7993 /**
@@ -87,7 +101,7 @@ public function mount(?FormView $form = null)
87101 public function submitFormOnRender (): void
88102 {
89103 if (!$ this ->getFormInstance ()->isSubmitted ()) {
90- $ this ->submitForm (false );
104+ $ this ->submitForm ($ this -> isValidated );
91105 }
92106 }
93107
@@ -103,18 +117,6 @@ public function getForm(): FormView
103117 return $ this ->formView ;
104118 }
105119
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-
118120 public function getFormName (): string
119121 {
120122 if (!$ this ->formName ) {
@@ -124,18 +126,19 @@ public function getFormName(): string
124126 return $ this ->formName ;
125127 }
126128
127- public function getFormValues (): array
129+ private function initializeFormValues (): void
128130 {
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 ());
134132 }
135133
136134 private function submitForm (bool $ validateAll = true ): void
137135 {
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 );
139142
140143 if ($ validateAll ) {
141144 // mark the entire component as validated
@@ -146,10 +149,19 @@ private function submitForm(bool $validateAll = true): void
146149 // we only want to validate fields in validatedFields
147150 // but really, everything is validated at this point, which
148151 // means we need to clear validation on non-matching fields
149- $ this ->clearErrorsForNonValidatedFields ($ this -> getFormInstance () , $ this -> getFormName ());
152+ $ this ->clearErrorsForNonValidatedFields ($ form , $ form -> getName ());
150153 }
151154
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 ()) {
153165 throw new UnprocessableEntityHttpException ('Form validation failed in component ' );
154166 }
155167 }
@@ -192,7 +204,7 @@ private function getFormInstance(): FormInterface
192204 return $ this ->formInstance ;
193205 }
194206
195- private function clearErrorsForNonValidatedFields (Form $ form , $ currentPath = '' ): void
207+ private function clearErrorsForNonValidatedFields (Form $ form , string $ currentPath = '' ): void
196208 {
197209 if (!$ currentPath || !\in_array ($ currentPath , $ this ->validatedFields , true )) {
198210 $ form ->clearErrors ();
0 commit comments