gnugat/wizard-bundle
A Symfony bundle for building multi-step wizards. Define ordered steps, guide users through a sequence of forms or actions, and keep navigation and flow logic organized within your Symfony application.
Start with an honest framing: this is not a Laravel package. gnugat/wizard-bundle is a Symfony 2 bundle, and its own README is tagged [DEPRECATED] use Symfony Flex instead. It expects app/AppKernel.php, app/console, and Symfony's Bundle/Kernel contracts. You cannot composer require it into a Laravel app and expect anything to happen — there is no service provider, no facade, no Artisan command.
So the practical "getting started" for a Laravel developer is:
AppKernel → append config → append routing → publish assets, collapsed into one interactive command.Command/InstallCommand.php (the entry point and CLI contract) and the Wizard/ directory (the step classes that do the actual file mutations). Everything else is Symfony 2 plumbing you can ignore.vendor:publish, and provider boot().First concrete use case: you maintain an internal package that isn't on Packagist, and you want php artisan acme:install to wire it up in one shot. Read this bundle's step model — a small set of composable, individually re-runnable steps — before writing your own install command.
If you genuinely want to run it (to study the diffs), you need a Symfony 2.x app:
composer require --dev gnugat/wizard-bundle
# register the wizard itself in app/AppKernel.php, then:
php app/console wizard:install acme/foo-bundle
Then git diff the app/AppKernel.php change. That diff is the whole value proposition.
The wizard's core behavior: take a package name, run composer require, detect the bundle class from the naming convention (acme/foo-bundle → Acme\FooBundle\AcmeFooBundle), and register it. Laravel solved this natively in 5.5 with package auto-discovery, so the day-to-day move is to declare it in the package's own composer.json and do nothing else:
{
"extra": {
"laravel": {
"providers": ["Acme\\Foo\\FooServiceProvider"],
"aliases": { "Foo": "Acme\\Foo\\Facades\\Foo" }
}
}
}
The consuming app then only runs composer require acme/foo. No source-file mutation, no wizard. If a consumer needs to opt out, they don't edit config/app.php — they add:
{
"extra": {
"laravel": { "dont-discover": ["acme/foo"] }
}
}
The wizard inserted new AcmeFooBundle(), into the dev array rather than the production $bundles array. The Laravel equivalent is an environment guard inside the provider:
public function register(): void
{
if ($this->app->environment(['local', 'testing'])) {
$this->app->register(\Acme\Foo\DebugServiceProvider::class);
}
}
Pair this with dont-discover in the consuming app if the package should never auto-register in production.
Instead of templating edits into AppKernel, package the "post-install wiring" as an explicit command that composes idempotent vendor:publish calls:
class InstallCommand extends Command
{
protected $signature = 'foo:install {--force : Overwrite published files}';
protected $description = 'Publish Foo config, migrations and assets';
public function handle(): int
{
$this->call('vendor:publish', [
'--provider' => \Acme\Foo\FooServiceProvider::class,
'--tag' => 'foo-config',
'--force' => $this->option('force'),
]);
$this->call('vendor:publish', [
'--provider' => \Acme\Foo\FooServiceProvider::class,
'--tag' => 'foo-migrations',
]);
if ($this->confirm('Run migrations now?', true)) {
$this->call('migrate');
}
$this->info('Foo installed. Add FOO_API_KEY to .env.');
return self::SUCCESS;
}
}
The wizard's lesson that carries over: keep each step small, make it safe to re-run, and print exactly what changed.
The wizard appended YAML to config.yml. Laravel's equivalent is mergeConfigFrom() in the provider plus a publishes() tag for the overridable copy:
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/foo.php', 'foo');
}
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/foo.php' => config_path('foo.php'),
], 'foo-config');
}
Consumers get sane defaults without touching a file, and can publish when they need to override.
If you copy the "one command" idea, hook it into Composer the way Laravel core does:
{
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
]
}
}
composer require will either refuse on platform constraints or install dead code. There is no support channel if it breaks.acme/foo-bundle → Acme\FooBundle\AcmeFooBundle). Any package that doesn't follow the convention silently fails to register. This is exactly the fragility Symfony Flex recipes and Laravel's explicit extra.laravel.providers were created to eliminate — prefer explicit declarations over conventions in anything you build.AppKernel.php and appends to config/routing files. There is no dry-run, diff preview, or uninstall path. If you build an installer, make those first-class: --dry-run, --diff, and a documented uninstall. This is the single most valuable takeaway from studying the package.vendor:publish handle overwrite semantics via --force.text(), select(), confirm(), suggest()), which is cleaner and has better UX. Use Prompts rather than hand-rolling $this->ask() chains — and use confirm() defaults deliberately so the "just press enter" path is the safe one.Command/InstallCommand.php and Wizard/ yields a requirements list for your own installer: idempotency, dry-run/diff, explicit class declarations over conventions, clear failure messages, and an uninstall path. After that, close it out.How can I help you explore Laravel packages today?