Weave 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

Extra Step Bundle Laravel Package

idci/extra-step-bundle

Symfony bundle for adding configurable extra steps to existing multi-step workflows. It provides services, routing, and templates to insert additional screens into forms, checkout, or other processes while preserving step order and state, extending flows without rewriting core logic.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check. This is a Symfony2 bundle, not a Laravel package. There is no extra.laravel block, no service provider, no auto-discovery. composer require idci/extra-step-bundle inside a Laravel app will either fail resolution (the transitive pin on symfony/* ^2.x/^3.x conflicts with anything modern) or, with --ignore-platform-reqs, install a pile of code that nothing in your kernel will ever load. So "getting started" for a Laravel developer means one of two paths, and you should pick deliberately:

Path A — you are actually in a legacy Symfony2 app (Sylius checkout era). Then:

  1. Confirm idci/step-bundle is installed and its multi-step flow already works. ExtraStepBundle only expands it; it does nothing standalone.
  2. Register the bundle in app/AppKernel.php (new IDCI\Bundle\ExtraStepBundle\IDCIExtraStepBundle()).
  3. Look first at the bundle's DependencyInjection/ directory — the config tree tells you far more than the README about how steps and orderings are declared. Then read the compiler pass, which is where the service tag for a step is collected.
  4. Your first use case: insert one extra step into the existing Sylius checkout between shipping and payment, with a trivial form and a template, and prove the navigation and state transitions still work end to end.

Path B — you are on Laravel (the realistic case). Read it, don't install it. The transferable artifact is the decomposition: a step registry (tagged services → a collected list) plus a path resolver (given the current step and user/order state, return the next step). That's two classes and an interface, and it's the whole useful idea.

Start here in your own codebase:

interface StepContract {
    public function key(): string;
    public function shouldShow(FlowState $state): bool; // conditional branching
    public function handle(Request $request, FlowState $state): StepResult;
}

final class StepRegistry { /* built from a tagged container binding */ }
final class PathResolver {
    /** @return list<string> */
    public function resolve(FlowState $state): array { /* ordered steps filtered by shouldShow() */ }
}

Register steps via a ServiceProvider using $this->app->tag([...], 'flow.steps') and resolve with $this->app->tagged('flow.steps'). That is the Laravel-native translation of the Symfony DI tag pattern this bundle is built on.

First use case to prove the pattern: a 4-step onboarding wizard where step 3 is conditional on an answer in step 1. If you can't express that cleanly, stop — you don't need the pattern yet.

Implementation Patterns

Tagged-service step registration (the core idiom). In Symfony you register a service, apply the bundle's step tag with an ordering/priority attribute, and the compiler pass collects it. Laravel equivalent:

$this->app->singleton(StepRegistry::class, function ($app) {
    return new StepRegistry($app->tagged('checkout.steps')); // order via a priority attribute
});

The important detail to copy is not the tagging mechanism but the priority/order attribute on the tag — ordering must be data, not if statements inside a controller.

Dynamic path resolution, computed once and persisted. The bundle's value is generating the path at runtime. The pattern that survives contact with production:

// on flow start and on any answer that can change branching
$path = $resolver->resolve($state);         // ['identity','documents','kyc','sign']
Flow::whereKey($id)->update(['path' => $path, 'position' => 0]);

Persist the resolved path. Recompute only when a shouldShow()-affecting field changes. Do not recompute on every request — this is the first performance hotspot, and it also makes your flows reproducible when you're debugging an incident.

Navigation as a projection, not a mutable pointer. Keep current_step derived from (path, position) rather than stored independently. Back/forward then becomes position ± 1 and you get resume-from-link for free. This is what the bundle does for you in Symfony; in Laravel it's ~40 lines and worth owning.

One controller per step, business logic off the controller. The bundle keeps each step in its own controller + form + template. Mirror that: a ShowStep/SubmitStep invokable controller delegating to StepContract::handle(). Validation rules live in a FormRequest, not the controller. This is the pattern that actually prevents the branching logic from metastasizing into a 600-line CheckoutController.

Middleware for step gating. In Laravel, a flow.step:kyc route middleware that 404s if the step isn't in the persisted path is the direct analogue of the bundle's transition enforcement. Cheap, testable, and prevents URL-hopping around a conditional branch.

Integration tip — model the transition, not just the step. Where this bundle sits alongside the host bundle's workflow/state machine, the durable lesson is: steps are UI, transitions are domain. Keep a flow_events table or fire domain events (StepCompleted, PathResolved) so the flow is auditable. You will be asked "why did this customer never see the documents step" and a log of resolved paths answers it.

Test the resolver in isolation. Table-driven tests over PathResolver with fixture FlowState objects are fast and catch the bugs that matter. Do not test branches through HTTP; you'll spend more time on fixtures than on logic.

Gotchas and Tips

Symfony 2.8 EOL (Nov 2018) is a hard wall. No security patches since. PHP 5.x-era code patterns (each(), create_function, old DI YAML/XML loaders, legacy PHPUnit constraints) break on PHP 8. If a legacy app is genuinely upgrading, this bundle is a blocker and a fork candidate — budget for that; don't discover it in a PHP 8.1 bump PR.

Composer resolution failure is the expected outcome, not a bug you fix. symfony/* ^2.x against Laravel 9+/Symfony 6/7 will not resolve. --ignore-platform-reqs is not a workaround, it's an operational liability — you've now installed untested code against an ABI it was never built for.

Zero stars, zero dependents, single-vendor provenance. Zero dependents on a Composer package is the strongest possible signal that nobody runs this in production outside the originating consultancy. No Stack Overflow corpus, no advisory feed, no successor package. Debugging means reading the source — including the base StepBundle's source, which the README barely documents. Budget 1–2 engineer-weeks to fluency in the Symfony2 bundle idioms before you write any code, and treat that as a cost against the alternative, not against zero.

Non-deterministic paths are a debugging tax you pay forever. A misconfigured shouldShow() produces a plausible-but-wrong flow that fails only at step N. Mitigations, all of which you must build yourself: log the resolved path with a version/hash of the resolver config, expose a "why this step" debug endpoint in non-prod, and snapshot-test the resolver's output for representative states.

Don't put this in a decision path for money, entitlements, or compliance. An unmaintained dependency resolving checkout paths is an audit finding waiting for a date. Substitute; don't ship.

DI tag drift after a fork. If you fork (Path B with a funded use case), the compiler pass and the tag names are the first thing to break silently on divergence. Add a container-compile test that asserts every expected step key is present — cheap, catches the whole class of "step vanished from the flow" bugs.

Extension points worth knowing, even if you only port the concept: tagged step services (registration), the event dispatcher around transitions (side effects, audit), kernel controller resolution plus routing conventions (step URLs), and the DI config tree (ordering). Those four are the seams. A Laravel reimplementation needs equivalents for each, and skipping the event seam is the most common mistake — it's where you'll later want notifications, analytics, and retries.

Substitution guidance, stated plainly. If you need dynamic multi-step flows on Laravel, use a maintained state-machine package or symfony/workflow standalone in-process, and layer the step registry/resolver pattern above it. If you're staying on Symfony, symfony/workflow plus your own resolver covers this bundle's capability with documentation, hireability, and security patches. Either way: preserve the requirement, not the dependency — and record the substitution decision so nobody re-opens the integration ticket in six months.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console