Come creare un comando artisan per Laravel

 Laravel ha un’interfaccia a line di comando chiamata Artisan che permette di velocizzare molte fasi del processo di sviluppo.
Tutti i comandi generati da Artisan vengono memorizzati nel percorso app/Console/Commands, ma è possibile posizionarli ovunque per poi  richiamarli tramite composer.
Al momento della scrittura dell’articolo è disponibile Laravel 5.6 ed è la documentazione alla quale mi rifarò.

Creare e registrare un Comando

Laravel è un framework davvero completo e ti aiuta a fare tante cose nel modo più veloce possibile.
Per questo ci basta scrivere in console make:console NomeClasse per creare un comando.
Così facendo NomeClasse sarà anche il comando che potremo successivamente lanciare dalla console.
In alternativa, è possibile tramite il parametro --command, assegnare un nome diverso dal nome della classe generata, come nell’esempio di seguito.
php artisan make:command QuizStart --command=quiz:start
Questo comando creerà la classe QuizStart in /app/console/commands
Il comando make di artisan dovrebbe in automatico inserire anche il path della classe appena creata nel file /app/console/Kernel.php, se così non fosse bisognerà aggiungerlo in questo modo:
protected $commands = [
    'App\Console\Commands\QuizStart'
];
Per controllare che il comando sia stato inserito nella lista di quelli disponibili tra il namespace che gli abbiamo assegnato, basterà lanciare
php artisan list
Nel caso in cui non fosse ancora disponibile, possiamo provare a pulire la cache di artisan con il comando:
php artisan quiz:start

Struttura di un file Command

Aprendo il file generato da artisan make, possiamo cambiare il nome e la descrizione, come nell’esempio qui sotto.
<?php
#/App/Console/Commands/QuizStart.php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class QuizStart extends Command
{
 /**
 * The name and signature of the console command.
 *
 * @var string
 */
 protected $signature = 'quiz:start';
 /**
 * The console command description.
 *
 * @var string
 */
 protected $description = 'Command description';
 /**
 * Create a new command instance.
 *
 * @return void
 */
 public function __construct()
 {
 parent::__construct();
 }

 /**
 * Execute the console command.
 *
 * @return mixed
 */
 public function handle()
 {
 //
 }

}

Stampare Messaggi

Per stampare messaggi in console esistono diversi comandi, a seconda di come si vuole formattare il messaggio.
Per testare i vari stili dei messaggi è possibile copiare e incollare l’esempio:

/**
 * Execute the console command.
 *
 * @return mixed
 */
public function handle()
{
    $this->line(“Semplice testo");
    $this->info("Hey, questo è un avviso guarda !");
    $this->comment(“Questo è un commento");
    $this->question(“Davvero hai qualcosa da chiedermi?");
    $this->error("Ops, qualcosa è andato storto.");
}

Input: arguments e options

Utilizzare Argomenti e opzioni

Nella property signature è possibile, insieme al nome del comando, definire anche gli arguments e le options di cui abbiamo bisogno per far lavorare il nostro script.

/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'quiz:start {user} {age} {--difficulty=} {--istest=}’;

Gli argument verranno registrati tramite la sintassi {nomeArgomento},
mentre le option {- -nomeOpzione=}.
Non è necessario creare i metodi get per recuperare le options, più avanti vedremo come fare.

La sintassi è molto semplice, questi sono i modi per poter utilizzare gli arguments e le options nei nostri comandi:

  • Argomento: quiz:start {argument}.
  • Argomento opzionale (da notare il punto interrogativo alla fine del nome dell’argument): quiz:start {argument?}.
  • Argomento con un valore di default: quiz:start {argument=defaultValue}.
  • Opzione Booleana: quiz:start –myOption.
  • Opzione con un valore: quiz:start –myOption=.
  • Opzione con un valore di default: quiz:start –myOption=12.
Recuperare i valori da argomenti e opzioni
Per poter recuperare gli argomenti e le opzioni all’interno del nostro comando possiamo usare la seguente sintassi:
/**
 * Execute the console command.
 *
 * @return mixed
 */
public function handle()
{
    // Get single Parameters
    $username = $this->argument('user');
    $difficulty = $this->option('difficulty');

    // Get all
    $arguments = $this->arguments();
    $options = $this->options();

    // Stop execution and ask a question 
    $answer = $this->ask('What is your name?');

    // Ask for sensitive information
    $password = $this->secret('What is the password?');

    // Choices
    $name = $this->choice('What is your name?', ['Taylor', 'Dayle'], $default);

    // Confirmation

    if ($this->confirm('Is '.$name.' correct, do you wish to continue? [y|N]')) {
        //
    }
}

Esempio

Nell’esempio che segue, mostro un quiz interattivo, dove bisognerà rispondere a tutte le domande e alla fine verranno mostrate in console, tutte le risposte date.

/**
 * Execute the console command.
 *
 * @return mixed
 */
public function handle()
{
    $difficulty =  $this->option('difficulty');

    if(!$difficulty){
        $difficulty = 'easy';
    }

    $this->line('Welcome '.$this->argument('user').", starting test in difficulty : ". $difficulty);

    $questions = [
        'easy' => [
            'How old are you ?', "What is the name of your mother?",
            'Do you have 3 parents ?','Do you like Javascript?',
            'Do you know what is a JS promise?'
        ],
        'hard' => [
            'Why the sky is blue?', "Can a kangaroo jump higher than a house?",
            'Do you think i am a bad father?','why the dinosaurs disappeared?',
            "why don't whales have gills?"
        ]
    ];

    $questionsToAsk = $questions[$difficulty];
    $answers = [];

    foreach($questionsToAsk as $question){
        $answer = $this->ask($question);
        array_push($answers,$answer);
    }

    $this->info("Thanks for do the quiz in the console, your answers : ");

    for($i = 0;$i <= (count($questionsToAsk) -1 );$i++){
        $this->line(($i + 1).') '. $answers[$i]);
    }
}

Eseguire un comando da un controller o una route

Naturalmente se abbiamo una interazione nel nostro comando non possiamo eseguirlo in una route o in un controller.
Nel caso in cui volessimo lanciare una routine possiamo farlo da controller, route e anche da altri comandi in questo modo:
$exitCode = Artisan::call('quiz:start', [
    'user' => 'Carlos', '--difficulty' => 'hard'
]);
Passando un array con gli arguments e le option che ci servono.

Inserire una barra di avanzamento

Alle volte, quando si realizza uno script di migrazione, come mi capita spesso, è utile avere una barra di avanzamento che indica a che punto si trova il nostro comando.
Per fare questo, Laravel ci viene incontro e ci da a disposizione un oggetto già bello e pronto.
Nell’esempio seguente, inizializzeremo l’oggetto e includeremo il suo avanzamento in un ciclo foreach, dopodiché chiameremo la fine dell’avanzamento quando avremo completato tutte le operazioni:
$bar = $this->output->createProgressBar(count($users));

foreach ($users as $user) {
    $this->performTask($user);

    $bar->advance();
}

$bar->finish();

Per quanto riguarda la progressbar esiste una documentazione dedicata, se ad esempio si vuole personalizzare lo stile.
Per approfondire l’argomento sui comandi invece, puoi fare riferimento alla documentazione ufficiale