In this article you’ll learn how to use Laravel’s Artisan command line tool, and how to create a customized command. Note that you need to be familiar with the Laravel framework to get the most of this article.
What are we building
In this tutorial we’re going to build a command to minify our css assets, which will be used like this:
cssmin 'output_path' 'file1'...'fileN' --comments --concat
output_path
: (required) path to save the minified files, (style.css
->style.min.css
).file1 ... fileN
: (required) list of files to minify.--comments
: (optional) add this option to keep comments.--concat
: (optional) concatenate the minified files into one file calledall.min.css
.
What is a Laravel Command
Artisan
is the name of the command line utility in Laravel. It comes with a set of predefined commands, which you can list with php artisan list
. If you want to show the help for a specific command, you can use php artisan help command
.
Creating the Css Minifier Command
To create an artisan command, you can use the command:make
command. This command accepts one argument:
name
: the class name for the command.
and three options:
--command
: the name that should be typed to run the command.--path
: by default the commands are stored within theapp/commands
folder, however, you can change that with this option.--namespace
: you can use this option to namespace your set of commands, e.g. in the commandcommand:make
, themake
command is under thecommand
namespace.
Now, to create our command we will use php artisan command:make CssMinCommand --command=cssmin
which will create a CssMinCommand.php
file within our app/commands
directory.
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class CssminCommand extends Command{
protected $name = 'cssmin';
protected $description = 'Command description.';
public function __construct(){
parent::__construct();
}
public function fire(){
//
}
protected function getArguments(){
return array(
array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions(){
return array(
array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}
Our CssMinCommand
class extends the Illuminate\Console\Command
and overrides two methods ( getArguments
, getOptions
).
getArguments
: this function returns an array of arguments that should be passed to the command, (ex: the list of files that we pass to thecssmin
command).getOptions
: returns a list of options or switches that you may pass to the command. (e.g.--comments
).
Note: options may or may not have values, --comments
is only a flag that returns true
if it’s passed to the command, whereas --ouptput='public/assets'
will return a value.
When your command is executed, the fire
method is called, so this is where we need to put our command logic.
Registering the Command
if you try to run our command php artisan cssmin 'args'
you’ll get a Command "cssmin" is not defined
.
To register a command you need to add it to the artisan.php
file:
Artisan::add( new CssMinCommand );
//or through the container
Artisan::add( App::make("CssMinCommand") );
If you don’t want to put your commands in the artisan.php
file, you can create a separate file and include it, or if you’re creating a package you can register them in your Service Provider.
Arguments
In our getArguments
method we will define our output
and files
.
To define an argument, we need to pass an array of values:
array( 'name', 'mode', 'description', 'defaultValue' )
name
: key name to be used when retrieving arguments.-
mode
: can have one of the three options:InputArgument::REQUIRED
: argument is required.InputArgument::OPTIONAL
: argument is optional.InputArgument::IS_ARRAY
: argument accepts multiple values ( ex:file1...fileN
).
However, you can combine them like
InputArgument::IS_ARRAY | InputArgument::REQUIRED
(argument is required and must be an array). description
: useful when printing the command help.defaultValue
: if argument was not provided.
So our getArguments
method will be:
protected function getArguments(){
return array(
array(
'output',
InputArgument::REQUIRED,
'Path to output directory'
),
array(
'files',
InputArgument::IS_ARRAY | InputArgument::OPTIONAL ,
"List of css files to minify"
),
);
}
Note: when using the IS_ARRAY
argument it should be the last one on the returned arguments array. (obviously).
Options
Our cssmin
command will only have two options. To define an option we pass an array:
array('name', 'shortcut', 'mode', 'description', 'defaultValue')
name
: the name of your option (ex:comments
).shortcut
: a shorter version of your option (ex:--verbose
and-v
).-
mode
: can be one of the four options (InputOption::VALUE_IS_ARRAY
,InputOption::VALUE_OPTIONAL
,InputOption::VALUE_REQUIRED
,InputOption::VALUE_NONE
), the first three values are similar to the arguments.VALUE_NONE
: indicates that the option is a boolean flag ( ex:--verbose
).
-
description
: useful when printing the command help. defaultValue
: if option value was not provided.
So our getOptions
method will be:
protected function getOptions(){
return array(
array('comments', 'c', InputOption::VALUE_NONE, 'Don\'t strip comments' , null),
array('concat', null, InputOption::VALUE_NONE, 'Concat the minified result to one file' , null),
);
}
Running the Command
When our fire
method is called we need to gather our arguments and options. We can make a separate function to do that for us:
private function init(){
// retrun an array
$this->files = $this->argument('files');
// return a string
$this->output_path = $this->argument('output');
// return true if passed, otherwise false
$this->comments = $this->option('comments');
// return true if passed, otherwise false
$this->concat = $this->option('concat');
}
The argument
and option
methods take a key as an argument and return the appropriate value.
To keep our example clean and simple we will use this simple function with a small modification for the minification process.
private function minify( $css, $comments ){
// Normalize whitespace
$css = preg_replace( '/\s+/', ' ', $css );
// Remove comment blocks, everything between /* and */, unless preserved with /*! ... */
if( !$comments ){
$css = preg_replace( '/\/\*[^\!](.*?)\*\//', '', $css );
}//if
// Remove ; before }
$css = preg_replace( '/;(?=\s*})/', '', $css );
// Remove space after , : ; { } */ >
$css = preg_replace( '/(,|:|;|\{|}|\*\/|>) /', '$1', $css );
// Remove space before , ; { } ( ) >
$css = preg_replace( '/ (,|;|\{|}|\(|\)|>)/', '$1', $css );
// Strips leading 0 on decimal values (converts 0.5px into .5px)
$css = preg_replace( '/(:| )0\.([0-9]+)(%|em|ex|px|in|cm|mm|pt|pc)/i', '${1}.${2}${3}', $css );
// Strips units if value is 0 (converts 0px to 0)
$css = preg_replace( '/(:| )(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/i', '${1}0', $css );
// Converts all zeros value into short-hand
$css = preg_replace( '/0 0 0 0/', '0', $css );
// Shortern 6-character hex color codes to 3-character where possible
$css = preg_replace( '/#([a-f0-9])\\1([a-f0-9])\\2([a-f0-9])\\3/i', '#\1\2\3', $css );
return trim( $css );
}//minify
Now to process our arguments (files) we’re going to make a separate method to do the job.
private function processFiles(){
// array of minified css
$css_result = [];
foreach ( $this->files as $file ) {
//read file content
$file_content = file_get_contents( $file );
//minify CSS and add it to the result array
$css_result[] = $this->minify( $file_content, $this->comments );
}//foreach
// if the concat flag is true
if( $this->concat ){
// join the array of minified css
$css_concat = implode( PHP_EOL, $css_result );
// save to file
file_put_contents($this->output_path . '/all.min.css', $css_concat);
}//if
else{
foreach ($css_result as $key => $css) {
//remove '.css' to add '.min.css'
$filename = basename( $this->files[$key], '.css' ) . '.min.css';
// save to file
file_put_contents($this->output_path . '/' . $filename, $css);
}//for
}//else
}//processFiles
Finally, our fire
method will only call the two methods:
public function fire(){
$this->init();
$this->processFiles();
}
Tip: You can also run an external command using the call
method.
$this->call('command:name', array('argument' => 'foo', '--option' => 'bar'));
To test our command, we’re going to copy some css files into our public/css
directory, and then run the command.
php artisan cssmin 'public/css' 'public/css/style.css' 'public/css/responsive.css'
php artisan cssmin 'public/css' 'public/css/style.css' 'public/css/responsive.css' --comments --concat
The first command will create two files (style.min.css
, responsive.min.css
) on the public/css
directory.
Because we used the --comments
and --concat
flags, we’re going to get a file called all.min.css
containing the two files with comments left.
Our command is not very descriptive and doesn’t give any messages or notifications!
Improving the Command
Before we continue, on the final GitHub repository I will create a new tag for our command so you can switch and test each one.
To make the command a little verbose, Laravel provides us with some output functions:
$this->line("This is a simple line message");
$this->info("This is an info");
$this->comment("This is a comment");
$this->question("This is a question");
$this->error("This is an error");
This will output:
Beside just displaying messages, you can ask the user for information, ex:
$confirm = $this->confirm("'style.min.css' already exists, overwrite?", false);
$filename = $this->ask("Save the file as?", 'all.min.css');
$choice = $this->choice(
'Please select a level of minication:',
[ 'minify all', 'leave comments' ],
'default value'
);
$password = $this->secret('Type your password to confirm:');
-
The
confirm
method takes two arguments, a question message and a default value if the user type something different thany/n
. -
The
ask
method will ask the user for an input instead of justy/n
, and if it’s left empty, the default value is returned. -
The
choice
method will give the user a numbered list to choose from, and if it’s left empty, the default value is returned. -
The
secret
method will prompt the user with a question and hide the typing, but the user input will be returned.
In fact, Laravel is just making Symfony’s Console API simpler and more verbose, and there is so much more if you want dig in.
Let’s make our command more verbose and keep the user updated about the performed tasks.
private function processFiles(){
$css_result = [];
foreach ( $this->files as $file ) {
$this->comment("'{$file}'");
$this->info("Loading file");
//read file content
$file_content = file_get_contents( $file );
$this->info("minifying");
//minify CSS and add it to the result array
$css_result[] = $this->minify( $file_content, $this->comments );
}//foreach
if( $this->concat ){
$this->comment("Concatenating into one file");
$css_concat = implode( PHP_EOL, $css_result );
$this->info("Saving to '{$this->output_path}/all.min.css'");
file_put_contents($this->output_path . '/all.min.css', $css_concat);
}//if
else{
foreach ($css_result as $key => $css) {
//remove '.css' to add '.min.css'
$filename = basename( $this->files[$key], '.css' ) . '.min.css';
$this->comment("Saving '{$filename}'");
file_put_contents($this->output_path . '/' . $filename, $css);
}//for
}//else
}//processFiles
Our function now prints some useful messages to keep track of what’s going on.
Note: This will be tagged as v2
of our command on the GitHub repository.
When creating an application, we are used to dumping the list of available routes (php artisan routes
).
Symfony provides a function that lets you print a such table easily. Check the documentation for an example. We’ll see next how we can use some Symfony Console Helpers.
Using the Symfony Console Helpers
To illustrate the use of some Symfony Helpers we will use the Progress Helper to keep the user updated about the job progress.
At the end of our init
method we will require a progress from the HelperSet
, then start our progress bar.
$this->progressbar = $this->getHelperSet()->get('progress');
$this->progressbar->start($this->output, count($this->files) );
The start
method accepts two arguments, $this->output
is a ConsoleOuput
instance from the Symfony Console. The second argument is the maximum number of steps.
Every time we process a file in our processFiles
method we will advance the progress bar by one step, and when the job is done we will end the progress bar and print a notification message.
private function processFiles(){
$css_result = [];
foreach ( $this->files as $file ) {
//read file content
$file_content = file_get_contents( $file );
//minify CSS and add it to the result array
$css_result[] = $this->minify( $file_content, $this->comments );
// sleep for one second to see the effect
//sleep(1);
$this->progressbar->advance();
}//foreach
if( $this->concat ){
$css_concat = implode( PHP_EOL, $css_result );
file_put_contents($this->output_path . '/all.min.css', $css_concat);
}//if
else{
foreach ($css_result as $key => $css) {
//remove '.css' to add '.min.css'
$filename = basename( $this->files[$key], '.css' ) . '.min.css';
file_put_contents($this->output_path . '/' . $filename, $css);
}//for
}//else
$this->progressbar->finish();
$this->info('Done');
}//processFiles
You can try the command with multiple files or uncomment the sleep
function line to see a live effect.
Note: This version will be tagged as v3
on the final repository.
Conclusion
In this article we’ve learned how create and extend Laravel commands. Laravel has a lot of built-in commands that you can explore, and you can also check our final repository on GitHub to test the final result. Questions? Comments? Would you like to see more Artisan Command tutorials? Let us know!
Share This Article
Younes is a freelance web developer, technical writer and a blogger from Morocco. He’s worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.
How to Create a Laravel CSS-Minify Command
http://grandparick.blogspot.com/2012/
How to Create a Laravel CSS-Minify Command
https://www.truenewsafrica.net/2020/06/20/bonjour-tout-le-monde/
How to Create a Laravel CSS-Minify Command
http://www.nolasurveyors.com/about/
How to Create a Laravel CSS-Minify Command
http://www.joyseafood.ltd/hello-world/
How to Create a Laravel CSS-Minify Command
https://seo-momentum.com/velkommen-til-bloggen-min/