2424use function Symfony \Component \String \u ;
2525
2626/**
27+ * Trait for managing multistep forms in Symfony UX LiveComponent.
28+ *
29+ * This trait simplifies the implementation of multistep forms by handling
30+ * step transitions, form validation, data persistence, and state management.
31+ * It provides a structured API for developers to integrate multistep forms
32+ * into their components with minimal boilerplate.
33+ *
2734 * @author Silas Joisten <[email protected] > 2835 * @author Patrick Reimers <[email protected] > 29- * @author Jules Pietri <[email protected] > 36+ * @author Jules Pietri <[email protected] > 3037 */
3138trait ComponentWithMultiStepFormTrait
3239{
@@ -42,6 +49,9 @@ trait ComponentWithMultiStepFormTrait
4249 #[LiveProp]
4350 public array $ stepNames = [];
4451
52+ /**
53+ * Checks if the current form has validation errors.
54+ */
4555 public function hasValidationErrors (): bool
4656 {
4757 return $ this ->form ->isSubmitted () && !$ this ->form ->isValid ();
@@ -50,38 +60,37 @@ public function hasValidationErrors(): bool
5060 /**
5161 * @internal
5262 *
53- * Must be executed after ComponentWithFormTrait::initializeForm()
63+ * Initializes the form and restores the state from storage.
64+ *
65+ * This method must be executed after `ComponentWithFormTrait::initializeForm()`.
5466 */
5567 #[PostMount(priority: -250 )]
5668 public function initialize (): void
5769 {
58- $ this ->currentStepName = $ this ->getStorage ()->get (
59- \sprintf ('%s_current_step_name ' , self ::prefix ()),
60- $ this ->formView ->vars ['current_step_name ' ],
61- );
70+ $ this ->currentStepName = $ this ->getStorage ()->get (\sprintf ('%s_current_step_name ' , self ::prefix ()), $ this ->formView ->vars ['current_step_name ' ]);
6271
6372 $ this ->form = $ this ->instantiateForm ();
6473
65- $ formData = $ this ->getStorage ()->get (\sprintf (
66- '%s_form_values_%s ' ,
67- self ::prefix (),
68- $ this ->currentStepName ,
69- ));
74+ $ formData = $ this ->getStorage ()->get (\sprintf ('%s_form_values_%s ' , self ::prefix (), $ this ->currentStepName ));
7075
7176 $ this ->form ->setData ($ formData );
7277
73- if ([] === $ formData ) {
74- $ this ->formValues = $ this ->extractFormValues ($ this ->getFormView ());
75- } else {
76- $ this ->formValues = $ formData ;
77- }
78+ $ this ->formValues = [] === $ formData
79+ ? $ this ->extractFormValues ($ this ->getFormView ())
80+ : $ formData ;
7881
7982 $ this ->stepNames = $ this ->formView ->vars ['steps_names ' ];
8083
8184 // Do not move this. The order is important.
8285 $ this ->formView = null ;
8386 }
8487
88+ /**
89+ * Advances to the next step in the form.
90+ *
91+ * Validates the current step, saves its data, and moves to the next step.
92+ * Throws a RuntimeException if no next step is available.
93+ */
8594 #[LiveAction]
8695 public function next (): void
8796 {
@@ -91,10 +100,7 @@ public function next(): void
91100 return ;
92101 }
93102
94- $ this ->getStorage ()->persist (
95- \sprintf ('%s_form_values_%s ' , self ::prefix (), $ this ->currentStepName ),
96- $ this ->form ->getData (),
97- );
103+ $ this ->getStorage ()->persist (\sprintf ('%s_form_values_%s ' , self ::prefix (), $ this ->currentStepName ), $ this ->form ->getData ());
98104
99105 $ found = false ;
100106 $ next = null ;
@@ -124,23 +130,21 @@ public function next(): void
124130 $ this ->form = $ this ->instantiateForm ();
125131 $ this ->formView = null ;
126132
127- $ formData = $ this ->getStorage ()->get (\sprintf (
128- '%s_form_values_%s ' ,
129- self ::prefix (),
130- $ this ->currentStepName ,
131- ));
133+ $ formData = $ this ->getStorage ()->get (\sprintf ('%s_form_values_%s ' , self ::prefix (), $ this ->currentStepName ));
132134
133- // I really don't understand why we need to do that. But what I understood is extractFormValues creates
134- // an array of initial values.
135- if ([] === $ formData ) {
136- $ this ->formValues = $ this ->extractFormValues ($ this ->getFormView ());
137- } else {
138- $ this ->formValues = $ formData ;
139- }
135+ $ this ->formValues = [] === $ formData
136+ ? $ this ->extractFormValues ($ this ->getFormView ())
137+ : $ formData ;
140138
141139 $ this ->form ->setData ($ formData );
142140 }
143141
142+ /**
143+ * Moves to the previous step in the form.
144+ *
145+ * Retrieves the previous step's data and updates the form state.
146+ * Throws a RuntimeException if no previous step is available.
147+ */
144148 #[LiveAction]
145149 public function previous (): void
146150 {
@@ -181,18 +185,31 @@ public function previous(): void
181185 $ this ->form ->setData ($ formData );
182186 }
183187
188+ /**
189+ * Checks if the current step is the first step.
190+ *
191+ * @return bool True if the current step is the first; false otherwise.
192+ */
184193 #[ExposeInTemplate]
185194 public function isFirst (): bool
186195 {
187196 return $ this ->currentStepName === $ this ->stepNames [array_key_first ($ this ->stepNames )];
188197 }
189198
199+ /**
200+ * Checks if the current step is the last step.
201+ *
202+ * @return bool True if the current step is the last; false otherwise.
203+ */
190204 #[ExposeInTemplate]
191205 public function isLast (): bool
192206 {
193207 return $ this ->currentStepName === $ this ->stepNames [array_key_last ($ this ->stepNames )];
194208 }
195209
210+ /**
211+ * Submits the form and triggers the `onSubmit` callback if valid.
212+ */
196213 #[LiveAction]
197214 public function submit (): void
198215 {
@@ -202,34 +219,35 @@ public function submit(): void
202219 return ;
203220 }
204221
205- $ this ->getStorage ()->persist (
206- \sprintf ('%s_form_values_%s ' , self ::prefix (), $ this ->currentStepName ),
207- $ this ->form ->getData (),
208- );
222+ $ this ->getStorage ()->persist (\sprintf ('%s_form_values_%s ' , self ::prefix (), $ this ->currentStepName ), $ this ->form ->getData ());
209223
210224 $ this ->onSubmit ();
211225 }
212226
227+ /**
228+ * Abstract method to be implemented by the component for custom submission logic.
229+ */
213230 abstract public function onSubmit ();
214231
215232 /**
216- * @return array<string, mixed>
233+ * Retrieves all data from all steps.
234+ *
235+ * @return array<string, mixed> An associative array of step names and their data.
217236 */
218237 public function getAllData (): array
219238 {
220239 $ data = [];
221240
222241 foreach ($ this ->stepNames as $ stepName ) {
223- $ data [$ stepName ] = $ this ->getStorage ()->get (\sprintf (
224- '%s_form_values_%s ' ,
225- self ::prefix (),
226- $ stepName ,
227- ));
242+ $ data [$ stepName ] = $ this ->getStorage ()->get (\sprintf ('%s_form_values_%s ' , self ::prefix (), $ stepName ));
228243 }
229244
230245 return $ data ;
231246 }
232247
248+ /**
249+ * Resets the form, clearing all stored data and returning to the first step.
250+ */
233251 public function resetForm (): void
234252 {
235253 foreach ($ this ->stepNames as $ stepName ) {
@@ -244,17 +262,33 @@ public function resetForm(): void
244262 $ this ->formValues = $ this ->extractFormValues ($ this ->getFormView ());
245263 }
246264
265+ /**
266+ * Abstract method to retrieve the storage implementation.
267+ *
268+ * @return StorageInterface The storage instance.
269+ */
247270 abstract protected function getStorage (): StorageInterface ;
248271
249272 /**
250- * @return class-string<FormInterface>
273+ * Abstract method to specify the form class for the component.
274+ *
275+ * @return class-string<FormInterface> The form class name.
251276 */
252277 abstract protected static function formClass (): string ;
253278
279+ /**
280+ * Abstract method to retrieve the form factory instance.
281+ *
282+ * @return FormFactoryInterface The form factory.
283+ */
254284 abstract protected function getFormFactory (): FormFactoryInterface ;
255285
256286 /**
257287 * @internal
288+ *
289+ * Instantiates the form for the current step.
290+ *
291+ * @return FormInterface The form instance.
258292 */
259293 protected function instantiateForm (): FormInterface
260294 {
@@ -264,20 +298,18 @@ protected function instantiateForm(): FormInterface
264298 $ options ['current_step_name ' ] = $ this ->currentStepName ;
265299 }
266300
267- return $ this ->getFormFactory ()->create (
268- type: static ::formClass (),
269- options: $ options ,
270- );
301+ return $ this ->getFormFactory ()->create (static ::formClass (), null , $ options );
271302 }
272303
273304 /**
274305 * @internal
306+ *
307+ * Generates a unique prefix based on the component's class name.
308+ *
309+ * @return string The generated prefix in snake case.
275310 */
276311 private static function prefix (): string
277312 {
278- return u (static ::class)
279- ->afterLast ('\\' )
280- ->snake ()
281- ->toString ();
313+ return u (static ::class)->afterLast ('\\' )->snake ()->toString ();
282314 }
283315}
0 commit comments