{"id":13579483,"url":"https://github.com/craue/CraueFormFlowBundle","last_synced_at":"2025-04-05T20:34:21.948Z","repository":{"id":1596522,"uuid":"2142752","full_name":"craue/CraueFormFlowBundle","owner":"craue","description":"Multi-step forms for your Symfony project.","archived":false,"fork":false,"pushed_at":"2024-06-26T19:19:15.000Z","size":1401,"stargazers_count":745,"open_issues_count":75,"forks_count":120,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-03-26T09:01:47.399Z","etag":null,"topics":["bundle","php","symfony","symfony-bundle"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"majksner/php-memcached-mamp","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/craue.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-08-02T13:55:48.000Z","updated_at":"2025-03-24T23:28:15.000Z","dependencies_parsed_at":"2023-07-09T12:33:09.024Z","dependency_job_id":"909b797f-8539-4160-b3fa-64b19529a755","html_url":"https://github.com/craue/CraueFormFlowBundle","commit_stats":{"total_commits":682,"total_committers":41,"mean_commits":"16.634146341463413","dds":"0.10850439882697949","last_synced_commit":"9e25a754df1ad1de3eee7c1209429bb6ab66147a"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craue%2FCraueFormFlowBundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craue%2FCraueFormFlowBundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craue%2FCraueFormFlowBundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craue%2FCraueFormFlowBundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/craue","download_url":"https://codeload.github.com/craue/CraueFormFlowBundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246786390,"owners_count":20833683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bundle","php","symfony","symfony-bundle"],"created_at":"2024-08-01T15:01:39.856Z","updated_at":"2025-04-05T20:34:18.721Z","avatar_url":"https://github.com/craue.png","language":"PHP","readme":"# Information\n\n[![Tests](https://github.com/craue/CraueFormFlowBundle/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/craue/CraueFormFlowBundle/actions/workflows/tests.yml)\n[![Coverage Status](https://coveralls.io/repos/github/craue/CraueFormFlowBundle/badge.svg?branch=master)](https://coveralls.io/github/craue/CraueFormFlowBundle?branch=master)\n\nCraueFormFlowBundle provides a facility for building and handling multi-step forms in your Symfony project.\n\nFeatures:\n- navigation (next, back, start over)\n- step labels\n- skipping of steps\n- different validation group for each step\n- handling of file uploads\n- dynamic step navigation (optional)\n- redirect after submit (a.k.a. \"Post/Redirect/Get\", optional)\n\nA live demo showcasing these features is available at http://craue.de/symfony-playground/en/CraueFormFlow/.\n\n# Installation\n\n## Get the bundle\n\nLet Composer download and install the bundle by running\n\n```sh\ncomposer require craue/formflow-bundle\n```\n\nin a shell.\n\n## Enable the bundle\n\nIf you don't use Symfony Flex, register the bundle manually:\n\n```php\n// in config/bundles.php\nreturn [\n\t// ...\n\tCraue\\FormFlowBundle\\CraueFormFlowBundle::class =\u003e ['all' =\u003e true],\n];\n```\n\nOr, for Symfony 3.4:\n\n```php\n// in app/AppKernel.php\npublic function registerBundles() {\n\t$bundles = [\n\t\t// ...\n\t\tnew Craue\\FormFlowBundle\\CraueFormFlowBundle(),\n\t];\n\t// ...\n}\n```\n\n# Usage\n\nThis section shows how to create a 3-step form flow for creating a vehicle.\nYou have to choose between two approaches on how to set up your flow.\n\n## Approach A: One form type for the entire flow\n\nThis approach makes it easy to turn an existing (common) form into a form flow.\n\n### Create a flow class\n\n```php\n// src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nuse Craue\\FormFlowBundle\\Form\\FormFlow;\nuse Craue\\FormFlowBundle\\Form\\FormFlowInterface;\nuse MyCompany\\MyBundle\\Form\\CreateVehicleForm;\n\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected function loadStepsConfig() {\n\t\treturn [\n\t\t\t[\n\t\t\t\t'label' =\u003e 'wheels',\n\t\t\t\t'form_type' =\u003e CreateVehicleForm::class,\n\t\t\t],\n\t\t\t[\n\t\t\t\t'label' =\u003e 'engine',\n\t\t\t\t'form_type' =\u003e CreateVehicleForm::class,\n\t\t\t\t'skip' =\u003e function($estimatedCurrentStepNumber, FormFlowInterface $flow) {\n\t\t\t\t\treturn $estimatedCurrentStepNumber \u003e 1 \u0026\u0026 !$flow-\u003egetFormData()-\u003ecanHaveEngine();\n\t\t\t\t},\n\t\t\t],\n\t\t\t[\n\t\t\t\t'label' =\u003e 'confirmation',\n\t\t\t],\n\t\t];\n\t}\n\n}\n```\n\n### Create a form type class\n\nYou only have to create one form type class for a flow.\nThere is an option called `flow_step` you can use to decide which fields will be added to the form\naccording to the step to render.\n\n```php\n// src/MyCompany/MyBundle/Form/CreateVehicleForm.php\nuse MyCompany\\MyBundle\\Form\\Type\\VehicleEngineType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass CreateVehicleForm extends AbstractType {\n\n\tpublic function buildForm(FormBuilderInterface $builder, array $options) {\n\t\tswitch ($options['flow_step']) {\n\t\t\tcase 1:\n\t\t\t\t$validValues = [2, 4];\n\t\t\t\t$builder-\u003eadd('numberOfWheels', ChoiceType::class, [\n\t\t\t\t\t'choices' =\u003e array_combine($validValues, $validValues),\n\t\t\t\t\t'placeholder' =\u003e '',\n\t\t\t\t]);\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\t// This form type is not defined in the example.\n\t\t\t\t$builder-\u003eadd('engine', VehicleEngineType::class, [\n\t\t\t\t\t'placeholder' =\u003e '',\n\t\t\t\t]);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tpublic function getBlockPrefix() {\n\t\treturn 'createVehicle';\n\t}\n\n}\n```\n\n## Approach B: One form type per step\n\nThis approach makes it easy to reuse the form types to compose other forms.\n\n### Create a flow class\n\n```php\n// src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nuse Craue\\FormFlowBundle\\Form\\FormFlow;\nuse Craue\\FormFlowBundle\\Form\\FormFlowInterface;\nuse MyCompany\\MyBundle\\Form\\CreateVehicleStep1Form;\nuse MyCompany\\MyBundle\\Form\\CreateVehicleStep2Form;\n\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected function loadStepsConfig() {\n\t\treturn [\n\t\t\t[\n\t\t\t\t'label' =\u003e 'wheels',\n\t\t\t\t'form_type' =\u003e CreateVehicleStep1Form::class,\n\t\t\t],\n\t\t\t[\n\t\t\t\t'label' =\u003e 'engine',\n\t\t\t\t'form_type' =\u003e CreateVehicleStep2Form::class,\n\t\t\t\t'skip' =\u003e function($estimatedCurrentStepNumber, FormFlowInterface $flow) {\n\t\t\t\t\treturn $estimatedCurrentStepNumber \u003e 1 \u0026\u0026 !$flow-\u003egetFormData()-\u003ecanHaveEngine();\n\t\t\t\t},\n\t\t\t],\n\t\t\t[\n\t\t\t\t'label' =\u003e 'confirmation',\n\t\t\t],\n\t\t];\n\t}\n\n}\n```\n\n### Create form type classes\n\n```php\n// src/MyCompany/MyBundle/Form/CreateVehicleStep1Form.php\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass CreateVehicleStep1Form extends AbstractType {\n\n\tpublic function buildForm(FormBuilderInterface $builder, array $options) {\n\t\t$validValues = [2, 4];\n\t\t$builder-\u003eadd('numberOfWheels', ChoiceType::class, [\n\t\t\t'choices' =\u003e array_combine($validValues, $validValues),\n\t\t\t'placeholder' =\u003e '',\n\t\t]);\n\t}\n\n\tpublic function getBlockPrefix() {\n\t\treturn 'createVehicleStep1';\n\t}\n\n}\n```\n\n```php\n// src/MyCompany/MyBundle/Form/CreateVehicleStep2Form.php\nuse MyCompany\\MyBundle\\Form\\Type\\VehicleEngineType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass CreateVehicleStep2Form extends AbstractType {\n\n\tpublic function buildForm(FormBuilderInterface $builder, array $options) {\n\t\t$builder-\u003eadd('engine', VehicleEngineType::class, [\n\t\t\t'placeholder' =\u003e '',\n\t\t]);\n\t}\n\n\tpublic function getBlockPrefix() {\n\t\treturn 'createVehicleStep2';\n\t}\n\n}\n```\n\n## Register your flow as a service\n\nXML\n```xml\n\u003cservices\u003e\n\t\u003cservice id=\"myCompany.form.flow.createVehicle\"\n\t\t\tclass=\"MyCompany\\MyBundle\\Form\\CreateVehicleFlow\"\n\t\t\tautoconfigure=\"true\"\u003e\n\t\u003c/service\u003e\n\u003c/services\u003e\n```\n\nYAML\n```yaml\nservices:\n    myCompany.form.flow.createVehicle:\n        class: MyCompany\\MyBundle\\Form\\CreateVehicleFlow\n        autoconfigure: true\n```\n\nWhen not using autoconfiguration, you may let your flow inherit the required dependencies from a parent service.\n\nXML\n```xml\n\u003cservices\u003e\n\t\u003cservice id=\"myCompany.form.flow.createVehicle\"\n\t\t\tclass=\"MyCompany\\MyBundle\\Form\\CreateVehicleFlow\"\n\t\t\tparent=\"craue.form.flow\"\u003e\n\t\u003c/service\u003e\n\u003c/services\u003e\n```\n\nYAML\n```yaml\nservices:\n    myCompany.form.flow.createVehicle:\n        class: MyCompany\\MyBundle\\Form\\CreateVehicleFlow\n        parent: craue.form.flow\n```\n\n## Create a form template\n\nYou only need one template for a flow.\nThe instance of your flow class is passed to the template in a variable called `flow` so you can use it to render the\nform according to the current step.\n\n```twig\n{# in src/MyCompany/MyBundle/Resources/views/Vehicle/createVehicle.html.twig #}\n\u003cdiv\u003e\n\tSteps:\n\t{% include '@CraueFormFlow/FormFlow/stepList.html.twig' %}\n\u003c/div\u003e\n{{ form_start(form) }}\n\t{{ form_errors(form) }}\n\n\t{% if flow.getCurrentStepNumber() == 1 %}\n\t\t\u003cdiv\u003e\n\t\t\tWhen selecting four wheels you have to choose the engine in the next step.\u003cbr /\u003e\n\t\t\t{{ form_row(form.numberOfWheels) }}\n\t\t\u003c/div\u003e\n\t{% endif %}\n\n\t{{ form_rest(form) }}\n\n\t{% include '@CraueFormFlow/FormFlow/buttons.html.twig' %}\n{{ form_end(form) }}\n```\n\n### CSS\n\nSome CSS is needed to render the buttons correctly. Load the provided file in your base template:\n\n```twig\n\u003clink type=\"text/css\" rel=\"stylesheet\" href=\"{{ asset('bundles/craueformflow/css/buttons.css') }}\" /\u003e\n```\n\n...and install the assets in your project:\n\n```sh\n# in a shell\nphp bin/console assets:install --symlink web\n```\n\n### Buttons\n\nYou can customize the default button look by using these variables to add one or more CSS classes to them:\n\n- `craue_formflow_button_class_last` will apply either to the __next__ or __finish__ button\n- `craue_formflow_button_class_finish` will specifically apply to the __finish__ button\n- `craue_formflow_button_class_next` will specifically apply to the __next__ button\n- `craue_formflow_button_class_back` will apply to the __back__ button\n- `craue_formflow_button_class_reset` will apply to the __reset__ button\n\nExample with Bootstrap button classes:\n\n```twig\n{% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {\n\t\tcraue_formflow_button_class_last: 'btn btn-primary',\n\t\tcraue_formflow_button_class_back: 'btn',\n\t\tcraue_formflow_button_class_reset: 'btn btn-warning',\n\t} %}\n```\n\nIn the same manner you can customize the button labels:\n\n- `craue_formflow_button_label_last` for either the __next__ or __finish__ button\n- `craue_formflow_button_label_finish` for the __finish__ button\n- `craue_formflow_button_label_next` for the __next__ button \n- `craue_formflow_button_label_back` for the __back__ button\n- `craue_formflow_button_label_reset` for the __reset__ button\n\nExample:\n\n```twig\n{% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {\n\t\tcraue_formflow_button_label_finish: 'submit',\n\t\tcraue_formflow_button_label_reset: 'reset the flow',\n\t} %}\n```\n\nYou can also remove the reset button by setting `craue_formflow_button_render_reset` to `false`.\n\n## Create an action\n\n```php\n// in src/MyCompany/MyBundle/Controller/VehicleController.php\npublic function createVehicleAction() {\n\t$formData = new Vehicle(); // Your form data class. Has to be an object, won't work properly with an array.\n\n\t$flow = $this-\u003eget('myCompany.form.flow.createVehicle'); // must match the flow's service id\n\t$flow-\u003ebind($formData);\n\n\t// form of the current step\n\t$form = $flow-\u003ecreateForm();\n\tif ($flow-\u003eisValid($form)) {\n\t\t$flow-\u003esaveCurrentStepData($form);\n\n\t\tif ($flow-\u003enextStep()) {\n\t\t\t// form for the next step\n\t\t\t$form = $flow-\u003ecreateForm();\n\t\t} else {\n\t\t\t// flow finished\n\t\t\t$em = $this-\u003egetDoctrine()-\u003egetManager();\n\t\t\t$em-\u003epersist($formData);\n\t\t\t$em-\u003eflush();\n\n\t\t\t$flow-\u003ereset(); // remove step data from the session\n\n\t\t\treturn $this-\u003eredirectToRoute('home'); // redirect when done\n\t\t}\n\t}\n\n\treturn $this-\u003erender('@MyCompanyMy/Vehicle/createVehicle.html.twig', [\n\t\t'form' =\u003e $form-\u003ecreateView(),\n\t\t'flow' =\u003e $flow,\n\t]);\n}\n```\n\n# Explanations\n\n## How the flow works\n\n1. Dispatch `PreBindEvent`.\n1. Dispatch `GetStepsEvent`.\n1. Update the form data class with previously saved data of all steps. For each one, dispatch `PostBindSavedDataEvent`.\n1. Evaluate which steps are skipped. Determine the current step.\n1. Dispatch `PostBindFlowEvent`.\n1. Create the form for the current step.\n1. Bind the request to that form.\n1. Dispatch `PostBindRequestEvent`.\n1. Validate the form data.\n1. Dispatch `PostValidateEvent`.\n1. Save the form data.\n1. Proceed to the next step.\n\n## Method `loadStepsConfig`\n\nThe array returned by that method is used to create all steps of the flow.\nThe first item will be the first step. You can, however, explicitly index the array for easier readability.\n\nValid options per step are:\n- `label` (`string`|`StepLabel`|`null`)\n\t- If you'd like to render an overview of all steps you have to set the `label` option for each step.\n\t- If using a callable on a `StepLabel` instance, it has to return a string value or `null`.\n\t- By default, the labels will be translated using the `messages` domain when rendered in Twig.\n- `form_type` (`FormTypeInterface`|`string`|`null`)\n\t- The form type used to build the form for that step.\n\t- This value is passed to Symfony's form factory, thus the same rules apply as for creating common forms. If using a string, it has to be the FQCN of the form type.\n- `form_options` (`array`)\n\t- Options passed to the form type of that step.\n- `skip` (`callable`|`bool`)\n\t- Decides whether the step will be skipped.\n\t- If using a callable...\n\t\t- it will receive the estimated current step number and the flow as arguments;\n\t\t- it has to return a boolean value;\n\t\t- it might be called more than once until the actual current step number has been determined.\n\n### Examples\n\n```php\nprotected function loadStepsConfig() {\n\treturn [\n\t\t[\n\t\t\t'form_type' =\u003e CreateVehicleStep1Form::class,\n\t\t],\n\t\t[\n\t\t\t'form_type' =\u003e CreateVehicleStep2Form::class,\n\t\t\t'skip' =\u003e true,\n\t\t],\n\t\t[\n\t\t],\n\t];\n}\n```\n\n```php\nprotected function loadStepsConfig() {\n\treturn [\n\t\t1 =\u003e[\n\t\t\t'label' =\u003e 'wheels',\n\t\t\t'form_type' =\u003e CreateVehicleStep1Form::class,\n\t\t],\n\t\t2 =\u003e [\n\t\t\t'label' =\u003e StepLabel::createCallableLabel(function() { return 'engine'; })\n\t\t\t'form_type' =\u003e CreateVehicleStep2Form::class,\n\t\t\t'form_options' =\u003e [\n\t\t\t\t'validation_groups' =\u003e ['Default'],\n\t\t\t],\n\t\t\t'skip' =\u003e function($estimatedCurrentStepNumber, FormFlowInterface $flow) {\n\t\t\t\treturn $estimatedCurrentStepNumber \u003e 1 \u0026\u0026 !$flow-\u003egetFormData()-\u003ecanHaveEngine();\n\t\t\t},\n\t\t],\n\t\t3 =\u003e [\n\t\t\t'label' =\u003e 'confirmation',\n\t\t],\n\t];\n}\n```\n\n# Advanced stuff\n\n## Validation groups\n\nTo validate the form data class bound to the flow, a step-based validation group is passed to the form type.\nBy default, if the flow's `getName` method returns `createVehicle`, such a group is named `flow_createVehicle_step1`\nfor the first step. You can customize this name by setting the flow's property `validationGroupPrefix` explicitly.\nThe step number (1, 2, 3, etc.) will be appended by the flow.\n\nCompared to standalone forms, setting the `validation_groups` option in your form type's `configureOptions`\nmethod won't have any effect in the context of a flow. The value is just ignored, i.e. will be overwritten by the flow.\nBut there are other ways of defining custom validation groups:\n\n- override the flow's `getFormOptions` method,\n- use the `form_options` step option, or\n- use the flow's `setGenericFormOptions` method.\n\nThe generated step-based validation group will be added by the flow, unless the `validation_groups` option is set to `false`, a closure, or a GroupSequence.\nIn this case, it will **not** be added by the flow, so ensure the step forms are validated as expected.\n\n## Disabling revalidation of previous steps\n\nTake a look at [#98](https://github.com/craue/CraueFormFlowBundle/issues/98) for an example on why it's useful to\nrevalidate previous steps by default. But if you want (or need) to avoid revalidating previous steps, add this to your flow class:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected $revalidatePreviousSteps = false;\n\n\t// ...\n\n}\n```\n\n## Passing generic options to the form type\n\nTo set options common for the form type(s) of all steps you can use method `setGenericFormOptions`:\n\n```php\n// in src/MyCompany/MyBundle/Controller/VehicleController.php\npublic function createVehicleAction() {\n\t// ...\n\t$flow-\u003esetGenericFormOptions(['action' =\u003e 'targetUrl']);\n\t$flow-\u003ebind($formData);\n\t$form = $flow-\u003ecreateForm();\n\t// ...\n}\n```\n\n## Passing step-based options to the form type\n\nTo pass individual options to each step's form type you can use the step config option `form_options`:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nprotected function loadStepsConfig() {\n\treturn [\n\t\t[\n\t\t\t'label' =\u003e 'wheels',\n\t\t\t'form_type' =\u003e CreateVehicleStep1Form:class,\n\t\t\t'form_options' =\u003e [\n\t\t\t\t'validation_groups' =\u003e ['Default'],\n\t\t\t],\n\t\t],\n\t];\n}\n```\n\nAlternatively, to set options based on previous steps (e.g. to render fields depending on submitted data) you can override method\n`getFormOptions` of your flow class:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\npublic function getFormOptions($step, array $options = []) {\n\t$options = parent::getFormOptions($step, $options);\n\n\t$formData = $this-\u003egetFormData();\n\n\tif ($step === 2) {\n\t\t$options['numberOfWheels'] = $formData-\u003egetNumberOfWheels();\n\t}\n\n\treturn $options;\n}\n```\n\n## Enabling dynamic step navigation\n\nDynamic step navigation means that the step list rendered will contain links to go back/forth to a specific step\n(which has been done already) directly.\nTo enable it, add this to your flow class:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected $allowDynamicStepNavigation = true;\n\n\t// ...\n\n}\n```\n\nIf you'd like to remove the parameters (added by using such a direct link) when submitting the form\nyou should modify the action for the opening form tag in the template like this:\n\n```twig\n{{ form_start(form, {'action': path(app.request.attributes.get('_route'),\n\t\tapp.request.query.all | craue_removeDynamicStepNavigationParameters(flow))}) }}\n```\n\n## Handling of file uploads\n\nFile uploads are transparently handled by Base64-encoding the content and storing it in the session, so it may affect performance.\nThis feature is enabled by default for convenience, but can be disabled in the flow class as follows:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected $handleFileUploads = false;\n\n\t// ...\n\n}\n```\n\nBy default, the system's directory for temporary files will be used for files restored from the session while loading step data.\nYou can set a custom one:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected $handleFileUploadsTempDir = '/path/for/flow/uploads';\n\n\t// ...\n\n}\n```\n\n## Enabling redirect after submit\n\nThis feature will allow performing a redirect after submitting a step to load the page containing the next step using a GET request.\nTo enable it, add this to your flow class:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nclass CreateVehicleFlow extends FormFlow {\n\n\tprotected $allowRedirectAfterSubmit = true;\n\n\t// ...\n\n}\n```\n\nBut you still have to perform the redirect yourself, so update your action like this:\n\n```php\n// in src/MyCompany/MyBundle/Controller/VehicleController.php\npublic function createVehicleAction() {\n\t// ...\n\t$flow-\u003ebind($formData);\n\t$form = $submittedForm = $flow-\u003ecreateForm();\n\tif ($flow-\u003eisValid($submittedForm)) {\n\t\t$flow-\u003esaveCurrentStepData($submittedForm);\n\t\t// ...\n\t}\n\n\tif ($flow-\u003eredirectAfterSubmit($submittedForm)) {\n\t\t$request = $this-\u003egetRequest();\n\t\t$params = $this-\u003eget('craue_formflow_util')-\u003eaddRouteParameters(array_merge($request-\u003equery-\u003eall(),\n\t\t\t\t$request-\u003eattributes-\u003eget('_route_params')), $flow);\n\n\t\treturn $this-\u003eredirectToRoute($request-\u003eattributes-\u003eget('_route'), $params);\n\t}\n\n\t// ...\n\t// return ...\n}\n```\n\n## Using events\n\nThere are some events which you can subscribe to. Using all of them right inside your flow class could look like this:\n\n```php\n// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php\nuse Craue\\FormFlowBundle\\Event\\GetStepsEvent;\nuse Craue\\FormFlowBundle\\Event\\PostBindFlowEvent;\nuse Craue\\FormFlowBundle\\Event\\PostBindRequestEvent;\nuse Craue\\FormFlowBundle\\Event\\PostBindSavedDataEvent;\nuse Craue\\FormFlowBundle\\Event\\PostValidateEvent;\nuse Craue\\FormFlowBundle\\Event\\PreBindEvent;\nuse Craue\\FormFlowBundle\\Form\\FormFlowEvents;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass CreateVehicleFlow extends FormFlow implements EventSubscriberInterface {\n\n\t/**\n\t * This method is only needed when _not_ using autoconfiguration. If it's there even with autoconfiguration enabled,\n\t * the `removeSubscriber` call ensures that subscribed events won't occur twice.\n\t * (You can remove the `removeSubscriber` call if you'll definitely never use autoconfiguration for that flow.)\n\t */\n\tpublic function setEventDispatcher(EventDispatcherInterface $dispatcher) {\n\t\tparent::setEventDispatcher($dispatcher);\n\t\t$dispatcher-\u003eremoveSubscriber($this);\n\t\t$dispatcher-\u003eaddSubscriber($this);\n\t}\n\n\tpublic static function getSubscribedEvents() {\n\t\treturn [\n\t\t\tFormFlowEvents::PRE_BIND =\u003e 'onPreBind',\n\t\t\tFormFlowEvents::GET_STEPS =\u003e 'onGetSteps',\n\t\t\tFormFlowEvents::POST_BIND_SAVED_DATA =\u003e 'onPostBindSavedData',\n\t\t\tFormFlowEvents::POST_BIND_FLOW =\u003e 'onPostBindFlow',\n\t\t\tFormFlowEvents::POST_BIND_REQUEST =\u003e 'onPostBindRequest',\n\t\t\tFormFlowEvents::POST_VALIDATE =\u003e 'onPostValidate',\n\t\t];\n\t}\n\n\tpublic function onPreBind(PreBindEvent $event) {\n\t\t// ...\n\t}\n\n\tpublic function onGetSteps(GetStepsEvent $event) {\n\t\t// ...\n\t}\n\n\tpublic function onPostBindSavedData(PostBindSavedDataEvent $event) {\n\t\t// ...\n\t}\n\n\tpublic function onPostBindFlow(PostBindFlowEvent $event) {\n\t\t// ...\n\t}\n\n\tpublic function onPostBindRequest(PostBindRequestEvent $event) {\n\t\t// ...\n\t}\n\n\tpublic function onPostValidate(PostValidateEvent $event) {\n\t\t// ...\n\t}\n\n\t// ...\n\n}\n```\n","funding_links":[],"categories":["PHP","Forms"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcraue%2FCraueFormFlowBundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcraue%2FCraueFormFlowBundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcraue%2FCraueFormFlowBundle/lists"}