In this day and age, it's quite normal for a developer to have an understanding of consoles, and how to issue basic commands. But what if you could code your own custom commands to improve your workflow? If we look back to Laravel 3, you might remember that it offered tasks. Tasks were extremely helpful, but still came up short for more complex operations. Thankfully, Laravel 4 packs a beefed up Artisan that will make your life as a developer so much easier!
What is Artisan?
Artisan is the command line utility that was released in Laravel 3.
If you're not familiar with Laravel, then you might not be aware of Artisan. Artisan is the command line utility that was released in Laravel 3. If you've used other frameworks, you can compare Artisan to Oil in FuelPHP, ZFTool in Zend, or the Console in Symfony 2.
Artisan offers many useful commands that can help you perform various tasks, such as generating migrations or publishing a package's assets. In addition to the built-in commands, you can extend Artisan with your own commands.
Artisan's Foundations
This is where Artisan draws its power.
In Laravel 3, Artisan was written from scratch by Taylor Otwell (Laravel's creator), thus it was quite basic (although still awesome). Now that Laravel 4 is Composer based, it can use existing packages developed by other brilliant developers. As a result, Laravel 4 now depends on numerous packages from the Symfony framework. One of these such packages is the excellent Console Component.
If we take a look at the source of the Artisan application at Illuminate\Console\Application
, we can see that the class, itself, extends Symfony\Component\Console\Application
. This is where Artisan draws its power. Although Artisan makes use of Symfony's Console component, a lot of the common methods have been given more fluent Laravel-like aliases. So don't worry, it'll still feel like you're developing with Laravel!
Common Questions
Two questions typically pop up, when attempting to develop a new command.
Q Where should I put commands?
When you install a copy of Laravel, you'll find a predefined directory at app/commands
. This directory is also in the classmap
of your composer.json
by default. This means that, once you've created a command, you'll need to run composer dump-autoload
to generate an updated autoload file. If you don't, you'll receive errors, complaining that your command cannot be found.
If you're developing a package, you'll need to create a directory within your packages src/<vendor>/<package>
directory to hold your commands. Throughout the Laravel 4 codebase, this directory is named Console
. Remember to ensure that the directory is autoloaded in your packages composer.json
.
Q How should I name commands?
Throughout the Laravel 4 codebase, all commands are suffixed with Command
, and are named after the task that they perform. Let's say, for example, that you have a command that clears your cache. You might name this command, CacheClearCommand
.
Basics of a Command
A command should perform a single task. Throughout the rest of this article, we're going to develop a user generator command. Let's review the basics of a command.
// app/commands/UserGeneratorCommand.php <?php use Illuminate\Console\Command; class UserGeneratorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'user:generate'; /** * The console command description. * * @var string */ protected $description = "Generate a new user"; /** * Execute the console command. * * @return void */ public function fire() { $this->line('Welcome to the user generator.'); } }
Laravel can generate this boilerplate code for you! Simply run:
$ php artisan command:make UserGeneratorCommand
This will create a skeleton command for you to modify, however, for the purposes of this tutorial, we'll go through the process of building a command from scratch, so that we can learn each piece of the process.
Command Name
All commands need to provide a name. This name is used to run the command from the console, and should describe the task that the command performs. While there is no convention for how your command is named, you might consider one of the following: namespace:group/command
, namespace:command
, or just command
.
Command Description
All commands need to provide a description. The description is used when retrieving a list of available commands from Artisan, and when viewing the help docs for a command. Descriptions should briefly describe the task that the command performs.
If we were to open our console and fetch a list of available commands, we still won't be able to see our command listed.
$ php artisan list Laravel Framework version 4.0.0 Usage: [options] command [arguments] Options: --help -h Display this help message. --quiet -q Do not output any message. --verbose -v Increase verbosity of messages. --version -V Display this application version. --ansi Force ANSI output. --no-ansi Disable ANSI output. --no-interaction -n Do not ask any interactive question. --env The environment the command should run under. Available commands: help Displays help for a command list Lists commands migrate Run the database migrations serve Serve the application on the PHP development server tinker Interact with your application workbench Create a new package workbench asset asset:publish Publish a package's assets to the public directory auth auth:reminders Create a migration for the password reminders table command command:make Create a new Artisan command config config:publish Publish a package's configuration to the application controller controller:make Create a new resourceful controller db db:seed Seed the database with records key key:generate Set the application key migrate migrate:install Create the migration repository migrate:make Create a new migration file migrate:refresh Reset and re-run all migrations migrate:reset Rollback all database migrations migrate:rollback Rollback the last database migration queue queue:listen Listen to a given queue queue:work Process the next job on a queue session session:table Create a migration for the session database table
To register our new command, open app/start/artisan.php
and have a quick read through the default comment block that's in there. When we run Artisan from the console, this file is included; we'll be using it to bootstrap our commands. In this file, we have access to an $artisan
variable that was declared prior to the file being included. Remember the Artisan application class that we looked at earlier? The one that extended the Symfony Console component? Well, $artisan
is an instance of that class.
Let's add our command to make it available in the console.
$artisan->add(new UserGeneratorCommand);
Or, if you prefer the static syntax:
Artisan::add(new UserGeneratorCommand);
The add
method accepts a command instance. Once our command has been added, we can access it from the console.
$ php artisan user:generate Welcome to the user generator.
The command should also be listed in the available commands, as well as the help information.
$ php artisan list
$ php artisan user:generate --help Usage: user:generate Options: --help (-h) Display this help message. --quiet (-q) Do not output any message. --verbose (-v) Increase verbosity of messages. --version (-V) Display this application version. --ansi Force ANSI output. --no-ansi Disable ANSI output. --no-interaction (-n) Do not ask any interactive question. --env The environment the command should run under.
If you receive any errors, ensure that you run
composer dump-autoload
from your applications root, after creating the command.
Colored Output
Outputting colored text to the console is a cinch in Artisan. There are four different helper methods to echo colored ANSI output.
$this->info("This is some information."); $this->comment("This is a comment."); $this->question("This is a question."); $this->error("This is an error.");
Options and Arguments
An exciting new feature to Artisan is the ability to provide a command with options and arguments.
Arguments
Arguments are strings that you send through to a command. They must be given to a command in the order that they are defined. Consider the following command:
$ php artisan user:generate [name] [email]
The name
argument must be specified before the email
argument.
Arguments can be defined as optional.
Options
Options are always optional, and are prefixed with two dashes, when provided.
$ php artisan user:generate --admin
Aside from being used as boolean switches, an option can be configured to accept a value or an array of values.
$ php artisan user:generate --name=Jason $ php artisan user:generate --role=user --role=editor
Defining Options and Arguments
Before we define our options and arguments, it's best to import the required Symfony classes (they're long and would be a pain to write out all the time). The two classes we need are Symfony\Component\Console\Input\InputOption
and Symfony\Component\Console\Input\InputArgument
.
Above our class declaration, we'll import both classes.
// app/commands/UserGenerateCommand.php <?php use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class UserGeneratorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'user:generate'; /** * The console command description. * * @var string */ protected $description = "Generate a new user"; /** * Execute the console command. * * @return void */ public function fire() { $this->line('Welcome to the user generator.'); } }
To define the options and arguments, you need to create two new methods: getArguments
and getOptions
. Both of these methods return an array of arguments or options. Let's make our command accept a name
argument, and an age
option.
// app/commands/UserGenerateCommand.php <?php use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class UserGeneratorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'user:generate'; /** * The console command description. * * @var string */ protected $description = "Generate a new user"; /** * Execute the console command. * * @return void */ public function fire() { $this->line('Welcome to the user generator.'); // Get the name arguments and the age option from the input instance. $name = $this->argument('name'); $age = $this->option('age'); $this->line("{$name} is {$age} years old."); } /** * Get the console command arguments. * * @return array */ protected function getArguments() { return array( array('name', InputArgument::REQUIRED, 'Name of the new user'), ); } /** * Get the console command options. * * @return array */ protected function getOptions() { return array( array('age', null, InputOption::VALUE_REQUIRED, 'Age of the new user') ); } }
Remember: Laravel can generate all of this boilerplate code for you. We're simply doing it manually for the purposes of reviewing each line in the class.
Now, we can provide a name
argument and an age
option from our console.
$ php artisan user:generate Jason --age=22 Jason is 22 years old.
Both arguments and options are defined as multi-dimensional arrays. Let's take a closer look at the definitions for each of them.
Argument Definitions
The array definition for an argument accepts four keys, with only the first one (the arguments name) being required. The second key is the arguments mode and should be either InputArgument::OPTIONAL
or InputArgument::REQUIRED
. The third is the description, and the fourth key is a default value, if the mode is set to InputArgument::OPTIONAL
.
Here's an argument using all the array keys.
array('name', InputArgument::OPTIONAL, 'Name of the new user', 'Jason')
Option Definitions
The array definition for an option accepts five keys with only the first (the options name) being required. The second key is a shortcut for the option (-a
for example). The third is the options mode and can be one of the following values: InputOption::VALUE_NONE
, InputOption::VALUE_REQUIRED
, InputOption::VALUE_OPTIONAL
, or InputOption::VALUE_IS_ARRAY
. The fourth key is the options description, and the fifth key is a default value, if the mode is not InputOption::VALUE_NONE
or InputOption::VALUE_REQUIRED
.
Here's an option, using all of the array keys.
array('age', 'a', InputOption::VALUE_OPTIONAL, 'Age of the new user', 22)
You can also combine the InputOption::VALUE_IS_ARRAY
mode with InputOption::VALUE_REQUIRED
or InputOption::VALUE_OPTIONAL
.
array('role', 'r', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Roles of the new user', 'user')
Confirmations and Questions
Yet another exciting new feature to Artisan is its ability to request confirmation, or even ask the user a question. This makes developing interactive commands as simple as possible.
Confirmations
Using confirm
, we can ask a user a question, and make them confirm with either "yes" or "no." Let's confirm that the user has entered their age correctly.
// app/commands/UserGenerateCommand.php <?php use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class UserGeneratorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'user:generate'; /** * The console command description. * * @var string */ protected $description = "Generate a new user"; /** * Execute the console command. * * @return void */ public function fire() { $this->line('Welcome to the user generator.'); // Get the name arguments and the age option from the input instance. $name = $this->argument('name'); $age = $this->option('age'); if ( ! $this->confirm("Are you really {$age} years old? [yes|no]", true)) { $this->comment('Then why did you say you were!?'); return; } $this->comment("{$name} is {$age} years old."); } /** * Get the console command arguments. * * @return array */ protected function getArguments() { return array( array('name', InputArgument::REQUIRED, 'Name of the new user'), ); } /** * Get the console command options. * * @return array */ protected function getOptions() { return array( array('age', null, InputOption::VALUE_REQUIRED, 'Age of the new user', null) ); } }
The first parameter is the question that you'd like to ask, and the second is the default value, if a user hits enter without typing anything.
Questions
Using ask
, we can ask a user a question and expect a response from them. Instead of killing our command if the user specifies that they entered their age incorrectly, let's instead ask them to enter it again.
// app/commands/UserGenerateCommand.php <?php use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class UserGeneratorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'user:generate'; /** * The console command description. * * @var string */ protected $description = "Generate a new user"; /** * Execute the console command. * * @return void */ public function fire() { $this->line('Welcome to the user generator.'); // Get the name arguments and the age option from the input instance. $name = $this->argument('name'); $age = $this->option('age'); // Confirm the user entered their age correctly and if they haven't we'll // ask them to enter it again. if ( ! $this->confirm("Are you really {$age} years old? [yes|no]", true)) { $age = $this->ask('So how old are you then?'); } $this->comment("{$name} is {$age} years old."); } /** * Get the console command arguments. * * @return array */ protected function getArguments() { return array( array('name', InputArgument::REQUIRED, 'Name of the new user'), ); } /** * Get the console command options. * * @return array */ protected function getOptions() { return array( array('age', null, InputOption::VALUE_REQUIRED, 'Age of the new user', null) ); } }
Command Dependencies
Dependency injection is an important step to ensure that your code is testable and future proof. Let's take our command a step further by injecting a model instance, so that we can generate the user. We'll begin by creating a user interface and user model.
// app/models/UserInterface.php <?php interface UserInterface { }
Our
UserInterface
doesn't actually define any method implementations, as this is just an example. For a real world application, you'd define the methods you'd expect on your model.
// app/models/User.php <?php class User extends Eloquent implements UserInterface { /** * The database table used by the model. * * @var string */ protected $table = 'users'; }
Now that we have our User
model implementing our UserInterface
, we can continue to setup our dependency in our command. I'm going to add a bit more to our generate command, and interact with the User
model that's injected.
// app/commands/UserGenerateCommand.php <?php use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class UserGeneratorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'user:generate'; /** * The console command description. * * @var string */ protected $description = "Generate a new user"; /** * User model instance. * * @var UserInterface */ protected $user; /** * Create a new UserGeneratorCommand instance. * * @param UserInterface $user * @return void */ public function __construct(UserInterface $user) { $this->user = $user; } /** * Execute the console command. * * @return void */ public function fire() { $this->line('Welcome to the user generator.'); // Get the name arguments and the age option from the input instance. $this->user->name = $this->argument('name'); $this->user->age = $this->option('age'); // Confirm the user entered their age correctly and if they haven't we'll // ask them to enter it again. We'll continue asking them until they are // sure they've entered the right age. $correctAge = false; while ( ! $correctAge) { if ( ! $this->confirm("Are you really {$this->user->age} years old? [yes|no]", true)) { $this->user->age = $this->ask('So how old are you then?'); } else { $correctAge = true; } } $this->user->framework = $this->ask('What is your favorite framework?', 'Laravel'); $this->user->website = $this->ask('What is your website address?'); // Save the user to the database. $this->user->save(); // Report that the user has been saved. $this->info("{$this->user->name} has been generated and saved."); } /** * Get the console command arguments. * * @return array */ protected function getArguments() { return array( array('name', InputArgument::REQUIRED, 'Name of the new user'), ); } /** * Get the console command options. * * @return array */ protected function getOptions() { return array( array('age', null, InputOption::VALUE_REQUIRED, 'Age of the new user', null) ); } }
The first thing you should notice is that the command now has a constructor. This constructor accepts a single parameter and we've type hinted the UserInterface
, so we know that the class we get implements the methods defined on the interface. Command constructors should also call the parent constructor.
In the fire
method of the command, we're directly assigning the properties on the User
model instance. We're also using a loop to continue asking the user if they have entered their age correctly. Lastly, the user is saved to the database and we output to the console that the user was generated and saved.
But wait! Before we can use the command, we need to inject an instance of our User
model.
// app/start/artisan.php $user = new User; $artisan->add(new UserGeneratorCommand($user));
If you have a database setup and configured correctly, you should now be able to run the command and have a new user saved to the database!
Package Commands
If you're developing a package in Laravel, you might want to include commands. Registering commands from packages is mostly the same process, except you don't (or can't) add the command in app/start/artisan.php
. Instead, you resolve them with Artisan from within your packages service provider.
// path/to/your/PackageServiceProvider.php /** * Register the service provider. * * @return void */ public function register() { $this->app['command.package.command'] = $this->app->share(function($app) { return new PackageCommand($app['dependency']); }); $this->commands('command.package.command'); }
The commands
method can accept any number of arguments, and will resolve the command out of the application container when Artisan is started.
Conclusion
When you compare Artisan in Laravel 4 to its Laravel 3 counterpart, you'll quickly find that the improvements are monumental. Commands can now be bound to the IoC container and include dependency injection, provide colored ANSI output, use arguments and options, and request user interaction.
The power of Artisan, thanks to Symfony's Console component, is unbelievable. Commands are going to play a huge role, as we move forward - so get on board early!
Comments