Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Formflow Bundle Laravel Package

craue/formflow-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require craue/formflow-bundle
    

    Enable the bundle in config/bundles.php (Symfony Flex) or AppKernel.php (legacy).

  2. First Use Case: Create a form flow controller extending Craue\FormFlowBundle\Controller\FormFlowController. Example:

    use Craue\FormFlowBundle\Controller\FormFlowController;
    use Symfony\Component\HttpFoundation\Request;
    
    class VehicleFlowController extends FormFlowController
    {
        public function createVehicleFlowAction(Request $request)
        {
            return $this->handleFlow(
                'vehicle_flow', // Unique flow name
                $request,
                'App\Form\VehicleType' // Your form type
            );
        }
    }
    
  3. Routing: Add a route targeting the controller method:

    # config/routes.yaml
    vehicle_flow:
        path: /vehicle/create
        controller: App\Controller\VehicleFlowController::createVehicleFlowAction
    
  4. Form Type: Define a form type with steps (e.g., VehicleType):

    use Craue\FormFlowBundle\Form\Flow\FormInterface;
    use Craue\FormFlowBundle\Form\Flow\FormStepInterface;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    
    class VehicleType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                ->add('make', TextType::class)
                ->add('model', TextType::class)
                ->add('year', IntegerType::class);
        }
    
        public function getSteps(FormInterface $flow): array
        {
            return [
                'basic_info' => [
                    'fields' => ['make', 'model'],
                    'label' => 'Basic Info',
                ],
                'details' => [
                    'fields' => ['year'],
                    'label' => 'Details',
                ],
            ];
        }
    }
    
  5. Template: Use the built-in template (formflow.html.twig) or extend it:

    {% extends 'formflow.html.twig' %}
    {% block formflow_steps %}
        {{ parent() }}
        <!-- Custom step navigation or logic -->
    {% endblock %}
    

Implementation Patterns

Core Workflow

  1. Step Definition:

    • Use getSteps() in your form type to define steps with:
      • fields: Array of field names for the step.
      • label: Human-readable step name.
      • Optional: validation_groups, skipable, redirect_route.
    • Example:
      public function getSteps(FormInterface $flow): array
      {
          return [
              'step1' => [
                  'fields' => ['field1', 'field2'],
                  'label' => 'Step 1',
                  'validation_groups' => ['step1'],
              ],
              'step2' => [
                  'fields' => ['field3'],
                  'label' => 'Step 2',
                  'skipable' => true,
              ],
          ];
      }
      
  2. Dynamic Navigation:

    • Override getSteps() dynamically based on user input or data:
      public function getSteps(FormInterface $flow): array
      {
          $data = $flow->getData();
          if ($data->isPremiumUser()) {
              return ['premium_step' => [...]];
          }
          return ['basic_step' => [...]];
      }
      
  3. Validation Groups:

    • Assign unique validation groups per step to validate only relevant fields:
      # config/validation.yaml
      App\Entity\Vehicle:
          constraints:
              - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: { fields: make, message: 'Make already exists.' }
          groups:
              step1:
                  - NotBlank: { groups: [step1], message: 'Make is required.' }
              step2:
                  - Range: { groups: [step2], min: 1900, max: date('Y') }
      
  4. File Uploads:

    • Handle file uploads per step by binding the form to an entity with @Assert\File constraints:
      $builder->add('document', FileType::class, [
          'label' => 'Upload Document',
          'mapped' => false, // Or true if storing in DB
          'constraints' => [
              new File(['maxSize' => '1024k', 'mimeTypes' => ['application/pdf']])
          ],
      ]);
      
  5. Post/Redirect/Get (PRG):

    • Enable PRG in the flow configuration:
      $this->handleFlow('flow_name', $request, 'App\Form\Type', [
          'prg' => true,
          'prg_route' => 'vehicle_flow_success',
      ]);
      

Integration Tips

  • Doctrine Integration: Bind the form to an entity (e.g., Vehicle) and persist after submission:

    public function createVehicleFlowAction(Request $request, EntityManagerInterface $em)
    {
        return $this->handleFlow('vehicle_flow', $request, 'App\Form\VehicleType', [
            'data_class' => Vehicle::class,
            'on_success' => function ($flow, $data) use ($em) {
                $em->persist($data);
                $em->flush();
            },
        ]);
    }
    
  • Event Listeners: Subscribe to flow events (e.g., FormFlowEvent) for custom logic:

    use Craue\FormFlowBundle\Event\FormFlowEvent;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    class FlowSubscriber implements EventSubscriberInterface
    {
        public static function getSubscribedEvents()
        {
            return [
                FormFlowEvent::PRE_SUBMIT => 'onPreSubmit',
            ];
        }
    
        public function onPreSubmit(FormFlowEvent $event)
        {
            $flow = $event->getFlow();
            if ($flow->getCurrentStep() === 'step1') {
                $flow->setDataClass('App\Entity\VehicleDraft');
            }
        }
    }
    
  • Twig Extensions: Access flow data in templates:

    {{ formflow.getCurrentStep() }}  {# Outputs current step name #}
    {{ formflow.getStepLabel('step1') }}  {# Outputs step label #}
    

Gotchas and Tips

Pitfalls

  1. Step Field Mismatch:

    • If getSteps() returns fields not present in the form, the flow will throw an exception.
    • Fix: Ensure fields in getSteps() match the form builder’s field names exactly.
  2. Data Binding Issues:

    • If the form is bound to an entity (data_class), ensure all steps’ fields are mapped to entity properties.
    • Fix: Use mapped => false for non-entity fields or add them to the entity.
  3. Validation Groups Not Triggering:

    • If validation fails silently, check:
      • The validation_groups in getSteps() match those in validation.yaml.
      • The form is submitted with the correct step context.
    • Fix: Debug with {{ dump(form.errors) }} in Twig.
  4. PRG Redirect Loops:

    • If PRG is enabled but the route is misconfigured, the flow may redirect infinitely.
    • Fix: Verify prg_route points to a valid route and handle the route’s controller logic.
  5. File Uploads and Steps:

    • Files uploaded in a step are not automatically cleared when navigating back.
    • Fix: Manually clear the file field in PRE_SUBMIT if needed:
      $event->getForm()->remove('file_field');
      

Debugging Tips

  • Log Flow State: Use the FormFlowEvent to log the current step or data:

    public function onPreSubmit(FormFlowEvent $event)
    {
        $this->logger->debug('Current step:', ['step' => $event->getFlow()->getCurrentStep()]);
    }
    
  • Check Flow Data: Dump the flow’s data object in a template:

    {{ dump(formflow.getData()) }}
    
  • Validate Step Configuration: Ensure getSteps() returns an associative array with step names as keys:

    // Correct:
    return ['step1' => [...], 'step2' => [...]];
    
    // Incorrect (will fail):
    return [[...], [...]];
    

Extension Points

  1. Custom Step Templates: Override the default step template (formflow/steps.html.twig) to customize rendering:

    {% extends 'formflow/steps.html.twig' %}
    {% block step_label %}
        <h2 class="custom-step-label">{{ label }}</h2>
    {% endblock %}
    
  2. Dynamic Step Addition: Add steps dynamically via FormFlowEvent::PRE_GET_STEPS:

    public function onPreGetSteps(FormFlowEvent $event)
    {
        $steps = $event->getSteps();
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware