Installation:
composer require craue/formflow-bundle
Enable the bundle in config/bundles.php (Symfony Flex) or AppKernel.php (legacy).
First Use Case:
Create a flow controller extending Craue\FormFlowBundle\Controller\FlowController:
use Craue\FormFlowBundle\Controller\FlowController;
use Craue\FormFlowBundle\Form\Flow\FormBuilderInterface;
class VehicleFlowController extends FlowController
{
public function buildForm(FormBuilderInterface $builder)
{
$builder
->addStep('vehicle_type', VehicleTypeStep::class)
->addStep('vehicle_details', VehicleDetailsStep::class)
->addStep('confirmation', ConfirmationStep::class);
}
}
Define Steps:
Create a step class (e.g., VehicleTypeStep) implementing Craue\FormFlowBundle\Form\Flow\Step\StepInterface:
use Craue\FormFlowBundle\Form\Flow\Step\StepInterface;
use Symfony\Component\Form\FormBuilderInterface;
class VehicleTypeStep implements StepInterface
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('type', ChoiceType::class, [
'choices' => ['Car' => 'car', 'Truck' => 'truck'],
]);
}
public function getName()
{
return 'vehicle_type';
}
}
Route the Flow:
# config/routes.yaml
vehicle_flow:
path: /vehicle-flow
controller: App\Controller\VehicleFlowController::newAction
src/Form/Flow/Step/ – Step implementations.src/Resources/config/services.xml – Bundle services.demo/ (in repo) – Example flow structure.Step Definition:
StepInterface with buildForm() and getName().FormBuilderInterface to define fields (like Symfony forms).$builder->add('make', TextType::class, ['validation_groups' => ['step1']]);
Flow Configuration:
addStep() in buildForm() for linear flows.getNextStep() in a step to skip steps:
public function getNextStep($currentStepName, FlowInterface $flow)
{
if ($flow->getData('vehicle_type') === 'truck') {
return 'confirmation'; // Skip details for trucks
}
return 'vehicle_details';
}
Data Handling:
$flow->getData() in steps or controllers.$vehicle = new Vehicle();
$vehicle->setType($flow->getData('vehicle_type'));
$em->persist($vehicle);
Validation:
validation_groups per step:
$builder->add('model', TextType::class, ['validation_groups' => ['step2']]);
@Assert\All({"step1": {...}, "step2": {...}})).File Uploads:
public function reset(FlowInterface $flow)
{
$flow->getData()->setUploadedFile(null);
}
{{ form_start(form) }} in a template, but wrap steps in:
{% for step in flow.steps %}
{% if step.isCurrent %}
{{ form_row(form[step.name]) }}
{% endif %}
{% endfor %}
config/packages/craue_formflow.yaml:
craue_formflow:
prg: true
Step Naming Collisions:
getName() returns unique step names (e.g., vehicle_type vs. vehicleType).flow->getSteps() for duplicates.Data Persistence:
FlowInterface::getData() to access current step data.Validation Groups:
validation_groups causes all validators to run on every step.File Uploads:
reset() in a step to handle cleanup:
public function reset(FlowInterface $flow)
{
if ($flow->getData()->getUploadedFile()) {
unlink($flow->getData()->getUploadedFile()->getPathname());
}
}
Dynamic Steps:
$flow = $this->get('craue_formflow.flow.vehicle_flow');
$flow->addStep('new_step', NewStep::class);
$this->get('logger')->debug('Current step:', ['step' => $flow->getCurrentStep()->getName()]);
$flow->getData() to verify submitted values.flow->getSteps() to confirm step sequence matches expectations.craue_formflow.flow.submit:
$eventDispatcher->addListener('craue_formflow.flow.submit', function (FlowEvent $event) {
$flow = $event->getFlow();
// Custom logic before submission
});
FlowController to modify default behavior (e.g., custom storage):
class CustomFlowController extends FlowController
{
protected function getFlowStorage()
{
return new DoctrineFlowStorage($this->getDoctrine()->getManager());
}
}
setOption() in buildForm() to attach metadata:
$builder->addStep('step1', Step1::class)->setOption('label', 'Custom Label');
Access via $step->getOption('label') in templates.prg: true causes infinite loops, ensure your submit route matches the flow’s getSubmitRoute().FlowStorageInterface:
craue_formflow:
storage:
class: App\Service\CustomFlowStorage
How can I help you explore Laravel packages today?