Using the wizard - shomisha/laravel-console-wizard GitHub Wiki
Using the wizard
The package allows its users to easily create wizards within the console, asking for complexly scripted human input and using and manipulating that input throughout the process.
Table of contents
Installation
Usage
Specifying wizard steps
Subwizards
Repeating steps
Inheriting answers from command args
Aborting the wizard
Performing actions based on user input
Generating wizards
Installation
In order to install the package simply run the composer require
command.
composer require shomisha/laravel-console-wizard
*Note: for Laravel 6 use version 1.3
composer require shomisha/laravel-console-wizard:1.3
Usage
In order to use the package, simply create a regular Artisan command (you can do so using the php artisan make:command CommandName
command) and have it extend the Shomisha\LaravelConsoleWizard\Command\Wizard
abstract class, instead of Laravel's default Illuminate\Console\Command
class. You still need to configure this class the way you would a regular Command
class, i.e. provide it with a $signature
and $description
properties.
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
class TestWizard extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'This is a test wizard.';
public function getSteps(): array
{
return [];
}
public function completed()
{
// TODO: implement the completed() method
}
}
Specifying wizard steps
The test class provided in the example below contains two methods, getSteps()
and completed()
, which are the two methods you are required to implement in order to extend the Wizard
class, as it is an abstract class.
The Wizard::getSteps(): array
method must return an array of objects implementing the Shomisha\LaravelConsoleWizard\Contracts\Step
interface, each of which represents a single step of your wizard. It is required that all members of the returned array implement the Step
interface as this is in fact validated and the Wizard will not start if this condition is not met and will throw an exception.
getSteps()
implementations
Valid public function getSteps(): array
{
return [];
}
public function getSteps(): array
{
return [
'name' => new \Shomisha\LaravelConsoleWizard\Steps\TextStep("What's your name?"),
'gender' => new \Shomisha\LaravelConsoleWizard\Steps\ChoiceStep("What's your gender", ['M', 'F']),
];
}
use \Shomisha\LaravelConsoleWizard\Steps\TextStep;
use \Shomisha\LaravelConsoleWizard\Steps\ChoiceStep;
public function getSteps(): array
{
return [
'name' => new TextStep("What's your name?"),
'gender' => new ChoiceStep("What's your gender", ['M', 'F']),
];
}
getSteps()
implementations:
Invalid public function getSteps()
{
return [
'user' => new \App\User(),
];
}
use App\User;
public function getSteps()
{
return [
'user' => new User(),
];
}
Basically anything that does not implement the Shomisha\LaravelConsoleWizard\Contracts\Step
may not be returned from the Wizard::getSteps(): array
method. You can read more about all the available steps on the Available steps wiki page.
Subwizards
In addition to providing steps, it is also possible to return instances of other wizards as steps. Doing this would result in the user being asked all of the questions in the base wizard, as well as being asked all of the questions of the subwizard. The order of the questions would be as defined in your getSteps()
implementation, where all the questions of a subwizard would be asked after the last question provided prior to the subwizard and before the first question provided after the subwizard.
In order to use a subwizard within a parent wizard you must instantiate the subwizard and pass it to the parent wizard's subWizard()
method, and return that with the array returned from your getSteps()
method. It is mandatory that the subwizard be passed through the subWizard()
, if it is returned as a step without this it would break the complete parent wizard.
As parent wizards, it is mandatory that subwizards also have their $signature
properties defined. This is a limitation imposed by Laravel's command structure, which in turn relies on Symfony's command structure and is required in order to keep your wizards operational.
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\ConfirmStep;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class SubWizard extends Wizard
{
/**
* We have to initialize and define a signature
* even if we never intend to invoke the subwizard on its own.
*/
protected $signature = 'wizard:controller-methods';
function getSteps(): array
{
return [
'methods' => new MultipleAnswerTextStep('What methods do you want in your controller?'),
];
}
function completed() {}
}
class ParentWizard extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'controller-name' => new TextStep("The name of your controller"),
'is-api' => new ConfirmStep("Is it an API controller?"),
'methods' => $this->subWizard(new SubWizard()),
'register-routes' => new ConfirmStep('Do you want to register routes for this controller?'),
];
}
function completed()
{
dd($this->answers->all());
}
}
The above example would produce the following output
shomisha:laravel-console-wizard shomisha$ php artisan wizard:test
The name of your controller:
> TestController
Is it an API controller? (yes/no) [no]:
> no
What methods do you want in your controller?
create
store
index
show
edit
update
delete
done
Do you want to register routes for this controller? (yes/no) [no]:
> yes
array:4 [
"controller-name" => "TestController"
"is-api" => false
"methods" => array:1 [
"controller-methods" => array:7 [
0 => "create"
1 => "store"
2 => "index"
3 => "show"
4 => "edit"
5 => "update"
6 => "delete"
]
]
"register-routes" => true
]
The input provided to the subwizard will be available in the parent wizard's $answers
storage under the key the subwizard is registered with in the form of an associative array. You can see a dump of the results from the example wizard in the output above.
It is important to keep in mind that the subwizards completed()
method will not get invoked. The subwizard will only be used for obtaining input and passing it through to the parent wizard. This way you can use a wizard as a standalone entity with performing all of its logic in the completed()
method, and still use the same wizard as a subwizard and not have to worry that its logic will break anything. However, the wizards modifier methods will get invoked as part of the input obtaining process. To learn more about these methods please read our Modifier methods wiki page.
OneTimeWizard
class
The Sometimes you may require to have a subwizard that is used solely as a subwizard and not as a standalone wizard, and you may need to use its specific set of steps only at a single place. It is redundant to create a complete wizard class just to serve as a subwizard for your parent wizard and never be used anywhere else. In order to accommodate these circumstances you may use the \Shomisha\LaravelConsoleWizard\Steps\OneTimeWizard
class. This class allows you to create an instance of a wizard at runtime, pass an array of steps for it to use through its constructor and use it as a fully legitimate subwizard. It is still required, however, that you wrap this subwizard with the parent's subWizard()
method.
Here is an example of how to use the OneTimeWizard
:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\OneTimeWizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:one-time-wizard';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'favourite-song' => $this->subWizard(
new OneTimeWizard([
'artist' => new TextStep('Your favourite song artist'),
'title' => new TextStep('Your favourite song title'),
])
),
'favourite-movie' => $this->subWizard(
new OneTimeWizard([
'director' => new TextStep('Your favourite movie director'),
'title' => new TextStep('Your favorite movie title'),
])
),
];
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:one-time-wizard
Your favourite song artist:
> Mayales
Your favourite song title:
> Od ljubavi
Your favourite movie director:
> Richard Curtis
Your favorite movie title:
> About Time
array:2 [
"favourite-song" => array:2 [
"artist" => "Mayales"
"title" => "Od ljubavi"
]
"favourite-movie" => array:2 [
"director" => "Richard Curtis"
"title" => "About Time"
]
]
shomisha:laravel-console-wizard shomisha$
Repeating steps
It is possible to repeat a specific step until a certain condition is met. This can be done by using the Wizard::repeat()
method. It is possible to repeat a step until it returns a specified value, a specified number of times, until a custom condition you have specified is satisfied and it is also possible to prompt the user after every run if the step should be repeated.
Here is an example of how to repeat a Wizard step:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'controller-name' => new TextStep('Enter the name of your controller'),
'controller-methods' => $this->repeat(
new TextStep('Enter a method for your controller, enter enough to stop')
)->untilAnswerIs('enough'),
];
}
function completed()
{
dd($this->answers->all());
}
}
This is the output produced by the above example:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:test
Enter the name of your controller:
> TestController
Enter a method for your controller, enter enough to stop:
> create
Enter a method for your controller, enter enough to stop:
> store
Enter a method for your controller, enter enough to stop:
> index
Enter a method for your controller, enter enough to stop:
> show
Enter a method for your controller, enter enough to stop:
> enough
array:2 [
"controller-name" => "TestController"
"controller-methods" => array:5 [
0 => "create"
1 => "store"
2 => "index"
3 => "show"
4 => "enough"
]
]
The example above repeats the step until its result is enough
. This is achieved by utilizing the Wizard::repeat()
method and then chaining it with the untilAnswerIs()
method. All of the repetition types are achieved by calling the wizard's repeat()
method and then chaining it with a method to control the number of repetitions. The chained methods are explained below.
untilAnswerIs()
The untilAnswerIs($answer)
will loop the step it is called upon until that step returns the value provided to the method as its answer. When dealing with this method it is important to keep in mind that the answer that ends the loop will be retained in the answers, and returned as part of the answer array. There is currently no way to remove it from the answers array using configuration, but this effect can be achieved using modifier methods.
In addition to the desired terminating answer, you may also pass in a second argument to the untilAnswerIs()
method, which is an integer and serves as the maximum number of repetitions you want to allow. This argument is optional. For instance, if you passed in 3 as the second argument your question would be repeated until the specified answer was provided or a total of 3 times. In this scenario, the third, terminating, answer would also be returned as an answer to this step.
An example of this type of repetition is provided in the introduction for repeating methods, just above this subheading.
times()
The times(int $times)
repetition method will loop the step it is called upon the number of times specified as the first argument to the method call. This is the simplest method and one to be used when you need an exact number of repetitions.
Here is an example of a Wizard that utilizes the times()
repetition method:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'favourite-songs' => $this->repeat(
new TextStep("Name 3 of your favourite songs")
)->times(3),
'favourite-movies' => $this->repeat(
new TextStep('Name 2 of your favourite movies')
)->times(2),
];
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces
shomisha:laravel-console-wizard shomisha$ php artisan wizard:test
Name 3 of your favourite songs:
> Ben Howard - End of The Affair
Name 3 of your favourite songs:
> Milo Greene - Heartless
Name 3 of your favourite songs:
> Dram - Nije sve kao sto izgleda
Name 2 of your favourite movies:
> The Prestige
Name 2 of your favourite movies:
> Predestination
array:2 [
"favourite-songs" => array:3 [
0 => "Ben Howard - End of The Affair"
1 => "Milo Greene - Heartless"
2 => "Dram - Nije sve kao sto izgleda"
]
"favourite-movies" => array:2 [
0 => "The Prestige"
1 => "Predestination"
]
]
withRepeatPrompt()
The withRepeatPrompt(string $question, bool $askOnFirstRun = false)
will keep repeating the step assigned to it for as long as the user confirms the question provided to it as the first argument. The second argument specifies whether the question will be prompted on the first run. This argument defaults to false, which means the repeating subwizard will be ran the first time without prompting the user for confirmation.
Here is an example of a wizard that repeats a step with a prompt:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:repeat-with-prompt';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'favourite-songs' => $this->repeat(
new TextStep('Name your favourite song')
)->withRepeatPrompt('Do you want to add another favourite song?'),
'favourite-movies' => $this->repeat(
new TextStep('Name of your favourite movie')
)->withRepeatPrompt('Do you want to add a favourite movie?', true),
];
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces
shomisha:laravel-console-wizard shomisha$ php artisan wizard:repeat-with-prompt
Name your favourite song:
> End of the Affair
Do you want to add another favourite song? (yes/no) [no]:
> yes
Name your favourite song:
> Palace - Heaven Out There
Do you want to add another favourite song? (yes/no) [no]:
> no
Do you want to add a favourite movie? (yes/no) [no]:
> yes
Name of your favourite movie:
> About Time
Do you want to add a favourite movie? (yes/no) [no]:
> yes
Name of your favourite movie:
> The Prestige
Do you want to add a favourite movie? (yes/no) [no]:
> no
array:2 [
"favourite-songs" => array:2 [
0 => "End of the Affair"
1 => "Palace - Heaven Out There"
]
"favourite-movies" => array:2 [
0 => "About Time"
1 => "The Prestige"
]
]
until()
The until(callable $callback)
method allows you to loop a step until a condition of your own making is satisfied. The first parameter you provide to the until()
method must be a callback which will return either true or false. If the callback returns false the loop will continue, and returning true will terminate the looping of the step. It is designed this way because the question should be repeated until a certain condition is met, i.e. true.
The callback you provide to the until()
method should receive a single argument. This argument represents the answer provided by the step being looped in the current iteration of the loop. It is important to keep in mind here that in the first iteration, before any steps have been executed, the callback will also be checked and it will receive null
as the value of the answer. It is up to you to take this case into account when implementing wizards.
The second argument you may pass to the until()
method is an integer which represents the maximum number of repetitions you want to allow. This argument results in the same behaviour it does with the untilAnswerIs()
method. It is also optional as it is in the untilAnswerIs()
method.
Here is an example of a Wizard that utilizes the until()
repetition method:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'evens' => $this->repeat(
new TextStep('Enter an equation that returns an even number')
)->until(function ($answer) {
if ($answer === null) {
return false;
}
return (bool) (eval("return {$answer};") % 2);
}, 3),
'odds' => $this->repeat(
new TextStep('Enter an equation that returns an odd number')
)->until(function ($answer) {
if ($answer === null) {
return false;
}
return ! (bool) (eval("return {$answer};") % 2);
}),
];
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:test
Enter an equation that returns an even number:
> 2
Enter an equation that returns an even number:
> 2 + 4
Enter an equation that returns an even number:
> 12 / 2
Enter an equation that returns an odd number:
> 25
Enter an equation that returns an odd number:
> 25 / 5
Enter an equation that returns an odd number:
> 21 / 3
Enter an equation that returns an odd number:
> 3 * 9
Enter an equation that returns an odd number:
> 2
array:2 [
"evens" => array:4 [
0 => "2"
1 => "2 + 4"
2 => "12 / 2"
]
"odds" => array:5 [
0 => "25"
1 => "25 / 5"
2 => "21 / 3"
3 => "3 * 9"
4 => "2"
]
]
Notice how the first step's loop was ended after 3 repetitions. This is because three was passed as the second argument to the first repetitions call and terminated the repetition even though the callback would have continued it. The looping of the second step was terminated only when the callback instructed the wizard so, where it ended as soon as it received 2 as an answer which is not an expression resulting in an odd number. As mentioned before, the answer that terminates the loop will be included in the overall answers returned from the loop.
Looping subwizards
It is highly advised that you read about modifier methods before reading about looping subwizards.
Looping subwizards is possible, of course, however it introduces some implications. As mentioned before, a looped step is treated as a single step, and any modifier methods that the parent wizard has for that step will be executed before starting the loop and after ending it, they will not be executed between iterations of a single loop. However, this applies to the modifier methods of the parent wizard. If you are repeating a subwizard, the complete subwizard will be executed on each iteration, which in turn means that the modifier methods that belong to the subwizard will be executed on each iteration as well.
Here is an example of looping a subwizard:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Contracts\Step;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class SubWizard extends Wizard
{
function getSteps(): array
{
return [
'name' => new TextStep('Name of the song'),
'artist' => new TextStep('Name of the artist'),
];
}
public function takingName(Step $step)
{
$this->line("Tell us about your favourite song");
}
function completed()
{}
}
class WizardTest extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'songs' => $this->repeat(
$this->subWizard(new Subwizard())
)->times(3),
];
}
public function takingSongs(Step $step)
{
$this->line("Enter 3 of your favourite songs:");
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:test
Enter 3 of your favourite songs:
Tell us about your favourite song
Name of the song:
> End of The Affair
Name of the artist:
> Ben Howard
Tell us about your favourite song
Name of the song:
> Comin' Home
Name of the artist:
> City and Color
Tell us about your favourite song
Name of the song:
> Time After Time
Name of the artist:
> Tuck and Patti
array:1 [
"songs" => array:3 [
0 => array:2 [
"name" => "End of The Affair"
"artist" => "Ben Howard"
]
1 => array:2 [
"name" => "Comin' Home"
"artist" => "City and Color"
]
2 => array:2 [
"name" => "Time After Time"
"artist" => "Tuck and Patti"
]
]
]
Take note of how the Enter 3 of your favourite songs:
line is displayed once, while the Tell us about your favourite song
is displayed three times. This is because the first line is outputted through a modifier method of the parent wizard, and because all three iterations of the subwizard are treated as a single step its modifier method will get executed only once. The second line is outputted through a modifier of the subwizard, and because everytime it gets executed, the complete subwizard will run, its modifier methods will run on each iteration of the loop, thus outputting the line three times in this example.
Excluding the last answer from the looped answer subset
It is possible to exclude the last answer from the looped answer subset. This is a very useful feature in combination with the untilAnswerIs()
method when you want to use a valid answer or an empty state as a signal to stop looping a step. In order to instruct your repetitive step to exclude the last answer you simply need to invoke its withoutLastAnswer()
method without any arguments.
Here is an example of how to utilize the withoutLastAnswer()
method:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:without-last-answer';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'favourite-songs' => $this->repeat(
new TextStep('Name your favourite song (leave blank to continue)')
)->untilAnswerIs(null)->withoutLastAnswer(),
'favourite-movies' => $this->repeat(
new TextStep('Name of your favourite movie (leave blank to continue)')
)->untilAnswerIs(null)->withoutLastAnswer(),
];
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:without-last-answer
Name your favourite song (leave blank to continue):
> Desecration Smile
Name your favourite song (leave blank to continue):
> Lost Along The Way
Name your favourite song (leave blank to continue):
>
Name of your favourite movie (leave blank to continue):
> The Good, The Bad and The Ugly
Name of your favourite movie (leave blank to continue):
>
array:2 [
"favourite-songs" => array:2 [
0 => "Desecration Smile"
1 => "Lost Along The Way"
]
"favourite-movies" => array:1 [
0 => "The Good, The Bad and The Ugly"
]
]
shomisha:laravel-console-wizard shomisha$
Inheriting input from command arguments and options
It is possible to allow your Wizard to accept answers from the command line arguments and options. This feature is disabled by default and you may enable it by overriding the protected bool $inheritAnswersFromArguments
property on your Wizard and setting it to true
. When this feature is enabled, the Wizard will check if you have provided any arguments or options with the same name as the step it is currently handling. If you have provided anything of the sort, it will not ask the step but take the provided input as the step's answer. It will not take the provided input as the answer if the input is null, however, but it will if it has any value.
The inputs you can provide are still bound by Laravel's rules for command input. In other words, you have to define any arguments and options you would want to pass in the command signature, you would have to make them optional if you didn't want to pass them every time, etc. You can learn about these rules on the official Laravel documentation page about console commands, especially in the "Defining Input Expectations" section.
Pay extra attention to the part about default option values. If you define a default value for an option it will always be used as a value for the step this option matches and you would never be prompted to complete that step.
Keep in mind that any input that you provide as command line arguments is still subject to all the manipulation step answers are subject to. In other words, if your Wizard is configured to perform validation, it will validate this input as well, if you have modifier methods defined for this step they will still be ran using the input from the command line.
Here's an example of how you can utilize this mechanism in a Wizard:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Contracts\Step;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:inherit-input {name?} {--age=} {--gender=m} {--country=}';
protected $description = 'Wizard description';
protected bool $inheritAnswersFromArguments = true;
function getSteps(): array
{
return [
'name' => new TextStep('Enter your name'),
'age' => new TextStep('Enter your age'),
'gender' => new TextStep('Enter your gender'),
'country' => new TextStep('Enter your country'),
];
}
public function answeredName(Step $step, string $name)
{
$this->line("Hello {$name}");
return $name;
}
function completed()
{
dd($this->answers->all());
}
}
And here's its output:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:inherit-input Misa --country=Serbia
Hello Misa
Enter your age:
> 22
array:4 [
"name" => "Misa"
"age" => "22"
"gender" => "m"
"country" => "Serbia"
]
shomisha:laravel-console-wizard shomisha$
Now this bit might be a little confusing but bear with me, I'll explain why something was or wasn't asked.
So you may notice how the first output after running the command is not a step question but a string saying "Hello Misa". This is because I provided a value for the name
step using the first command line argument, so the Wizard did not ask that step but went straight to its answered modifier method which contains a $this->line()
call and prints the greeting to the console.
Next the age question is asked. Even though a parameter named age
is defined in the command signature, the step is still asked since no value for it is provided.
And then we skip directly to the completed()
method. No more steps are asked and their answers are present in the dumped data, despite two more being defined. This is due to the third step, the gender
one, having a default value as a command line parameter. Because of this it will always have this value and the user will never be asked to answer this step. And the last step, country
, simply isn't ran because the command line contains a --country
option with a defined value and that value is used as an answer to the country
step.
Aborting the wizard
It is possible to programmatically abort the wizard at runtime, should you require to do so. This ability is particularly useful with wizard validation about which you can read later on in the wiki.
To abort an ongoing wizard simply invoke its abort(string $message = null)
method. You may pass in a string to this method if you want, and this string will be outputted as an error, i.e. in red, however this argument is optional and you may call abort()
without any parameters.
Here is an example of aborting a wizard:
<?php
namespace App\Console\Commands;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
class WizardTest extends Wizard
{
protected $signature = 'wizard:test';
protected $description = 'Wizard description';
function getSteps(): array
{
return [
'name' => new TextStep('What is your name?'),
'country' => new TextStep('Where do you come from?'),
'age' => new TextStep('How old are you?'),
];
}
public function answeredCountry($step, $country)
{
$this->abort("That is enough!");
}
function completed()
{
dd($this->answers->all());
}
}
And here is the output it produces:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:test
What is your name?:
> Misa
Where do you come from?:
> Serbia
That is enough!
shomisha:laravel-console-wizard shomisha
Take note of how the age
step was never taken, nor was the dd()
line in completed()
executed. This is because the answeredCountry()
modifier invokes the abort()
method thus outputting the string provided to it and terminating the wizard.
Performing actions based on user input
Merely collecting input does not create much value for the user without being able to do something with that input. The Wizard::completed()
method is the one invoked after the wizard runs through all of the steps and prompts the user for input based on all of them. This method should contain the core logic of your command, and also perform any actions that depend on the user-provided data.
To access the input provided by your user you may use the Wizard::answers
property. This is merely an instance of the Illuminate\Support\Collection
class that contains all the data input by the user, keyed in the same way you keyed your steps. This allows you to access answers using the Collection::get()
method like in the example below
function getSteps(): array
{
return [
'first-name' => new TextStep('Your first name'),
'last-name' => new TextStep('Your last name'),
];
}
function completed()
{
$firstName = $this->answers->get('first-name');
$lastName = $this->answers->get('last-name');
}
In addition to performing actions using the Wizard::completed()
method you may also place your logic in modifier methods. You can read more about these on the Modifier methods wiki page. Note that the completed()
method will not be ran when a wizard is being used as a subwizard.
Generating wizards
In order to speed up the process of creating console wizards you may use an Artisan command for generating wizards automatically. This command guides you through a wizard in the console that asks you several questions and generates a Wizard based on your input. You can utilize this mechanism by running the php artisan wizard:generate
command in the terminal from your project root. This command will generate a wizard and save it in your app/Console/Command
directory.
Here is an example of how it works:
shomisha:laravel-console-wizard shomisha$ php artisan wizard:generate
Enter the class name for your wizard:
> CreateAdminWizard
Enter the signature for your wizard:
> admin:create
Enter the description for your wizard:
> Creates an instance of the Admin model.
Do you want to add a wizard step? (yes/no) [no]:
> yes
Enter step name:
> name
Enter step question:
> Enter admin name
Choose step type:
[0] Text step
[1] Multiple answer text step
[2] Choice step
[3] Multiple choice step
[4] Unique multiple choice step
[5] Confirm step
> 0
Do you want a 'taking' modifier method for this step? (yes/no) [no]:
> no
Do you want an 'answered' modifier method for this step? (yes/no) [no]:
> no
Do you want to add a wizard step? (yes/no) [no]:
> yes
Enter step name:
> email
Enter step question:
> Enter admin email
Choose step type:
[0] Text step
[1] Multiple answer text step
[2] Choice step
[3] Multiple choice step
[4] Unique multiple choice step
[5] Confirm step
> 0
Do you want a 'taking' modifier method for this step? (yes/no) [no]:
> no
Do you want an 'answered' modifier method for this step? (yes/no) [no]:
> yes
Do you want to add a wizard step? (yes/no) [no]:
> yes
Enter step name:
> permissions
Enter step question:
> Choose admin permisions
Choose step type:
[0] Text step
[1] Multiple answer text step
[2] Choice step
[3] Multiple choice step
[4] Unique multiple choice step
[5] Confirm step
> 4
Add option for multiple choice (enter 'stop' to stop):
> users
Add option for multiple choice (enter 'stop' to stop):
> admins
Add option for multiple choice (enter 'stop' to stop):
> posts
Add option for multiple choice (enter 'stop' to stop):
> comments
Add option for multiple choice (enter 'stop' to stop):
> products
Add option for multiple choice (enter 'stop' to stop):
> stop
Do you want a 'taking' modifier method for this step? (yes/no) [no]:
> no
Do you want an 'answered' modifier method for this step? (yes/no) [no]:
> no
Do you want to add a wizard step? (yes/no) [no]:
> no
Wizard created successfully.
And here is the wizard generated by the input from the example above:
<?php
namespace App\Console\Command;
use Shomisha\LaravelConsoleWizard\Command\Wizard;
use Shomisha\LaravelConsoleWizard\Contracts\Step;
use Shomisha\LaravelConsoleWizard\Steps\TextStep;
use Shomisha\LaravelConsoleWizard\Steps\UniqueMultipleChoiceStep;
class CreateAdminWizard extends Wizard
{
protected $signature = 'admin:create';
protected $description = 'Creates an instance of the Admin model.';
public function getSteps() : array
{
return ['name' => new TextStep('Enter admin name'), 'email' => new TextStep('Enter admin email'), 'permissions' => new UniqueMultipleChoiceStep('Choose admin permisions', ['users', 'admins', 'posts', 'comments', 'products'])];
}
public function answeredEmail(Step $step, $email)
{
return $email;
}
public function completed()
{
return $this->answers->all();
}
}
In just a few seconds, by answering several questions via your terminal, you can get a fully prepared wizard and focus on implementing your completed
method and your modifier methods if you need them.