Se você já trabalhou com o Laravel, certamente percebeu a praticidade de seus comandos artisan durante o desenvolvimento. É provável que já tenha empregado comandos como artisan make:model ou artisan make:controller, o que aumenta significativamente a eficiência no trabalho. Contudo, alguns desenvolvedores desconhecem a possibilidade de criar comandos personalizados para automatizar tarefas específicas em seus sistemas.

Gerando comandos no Laravel

Criar um comando no Laravel é uma tarefa bastante simples – você só precisa utilizar o seguinte comando:

php artisan make:command SendNotification

O comando mencionado acima resultará na criação do arquivo app/Console/Commands/SendNotification.php, o qual conterá o seguinte conteúdo:

class SendNotification extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:send-notification';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     */
    public function handle()
    {

    }
}

Ele contém dois atributos: $signature e $description e um método handle(). O atributo $signature representa o nome e a assinatura (com argumentos, se houver) que será usado para invocar o comando pelo artisan. Já o atributo $description deve conter uma descrição sucinta do comando, que será exibida ao listar todos os comandos disponíveis. No método handle() é onde implementaremos as ações que o nosso comando executará. Segue uma implementação simples.

<?php

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;
use App\Notifications\EmailNotification;

class SendNotification extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:send-notification';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Envia notificações para os usuários do sistema';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        User::all()
             ->each(function ($user) { 
                $user->notify(new EmailNotification(
                    subject: 'Olá',
                    body: 'Olá, ' . $user->name . ' como vai você!'
                ));
             });
    }
}

Ao executar o comando php artisan list“, o comando será listado com a signature e description informados.

Listagem dos comandos do Laravel com o comando app:send-notification incluido.

Dessa forma, podemos executar nosso comando utilizando php artisan app:send-notification.

Ao ser executado o comando, o método handle() será chamado automaticamente pelo framework, então podemos incluir qualquer dependência nele, que o mecanismo de injeção de dependências do Laravel cuidará de injetá-la para nós.

public function handle(MyService $myService)
    {
        $result = $myService->doSomething();

        $this->info($result);
    }

Uma alternativa à criação de uma classe para um comando é utilizar uma closure. Para isso, utilizamos o método command da classe Artisan, chamando-o em routes/console.php, o qual é carregado automaticamente juntamente com as classes de comando pelo app/Console/Kernel.php. Vamos observar como nosso comando seria implementado como uma closure.

Artisan::command('app:send-notification', function () {
    User::all()
         ->each(function ($user) { 
            $user->notify(new EmailNotification(
                subject: 'Olá',
                body: 'Olá, ' . $user->name . ' como vai você!'
            ));
         });
})->purpose('Envia notificações para os usuários do sistema');

No método command, o primeiro parâmetro é a assinatura do comando, e o segundo é a closure que será executada. Temos a opção de encadear o método purpose() para adicionar a descrição do comando.

Entrada de dados

Quando criamos um comando muitas vezes é necessário ele receber alguns valores para executar a tarefa. Podemos fazer isso de duas formas: de argumentos na chamada do comando ou perguntando para o usuario.

Argumentos na chamada do comando

Para receber valores como argumentos, primeiro devemos colocá-los entre chaves na assinatura do comando, por exemplo, app:send-notification {subject} {body}, e depois recuperá-los dentro do método handle() utilizando o método $this->argument(<nome na assinatura>). Veja um exemplo.

    protected $signature = 'app:send-notification {subject} {body}';

    public function handle()
    {
        $subject = $this->argument('subject');
        $body = $this->argument('body');

        User::all()
             ->each(function ($user) use($subject, $body) {
                $user->notify(new EmailNotification(
                    subject: $subject,
                    body: str_replace( '%name%', $user->name, $body)
                ));
             });
    }

Agora, podemos executar o comando passando os argumentos usando: php artisan app:send-notification "Mensagem de Olá!" "%name%, tenha um bom dia.". No entanto, é importante lembrar que, a partir de agora, se você não fornecer os argumentos necessários para o comando, ele resultará em um erro.

Mensagem de erros se os argumentos do comando não forem informados.
Mensagem de erros se os argumentos do comando não forem informados.

É possível tornar os argumentos opcionais adicionando “?” após seus nomes na assinatura (app:send-notification {subject?} {body?}), o que faz com que o método $this->argument('subject') retorne null, caso não seja fornecido. Além disso, é possível fornecer valores padrão para os argumentos na assinatura, indicando o nome seguido pelo sinal de “=” e o valor a ser atribuído caso nenhum seja explicitamente passado.

// argumentos com valores opcionais
app:send-notification {subject?} {body?}

//argumentos com valores padrões
app:send-notification {subject="Mensagem de Olá"} {body="Olá %name%, prazer em te conhecer"}

Uma alternativa para a entrada de argumentos é o option, que é especificado com dois traços “” antes do nome do argumento e pode ou não ter um valor associado. Se não tiver um valor, ele atua como um interruptor de ligar/desligar, fazendo com que o método $this->option('nome do option') retorne true ou false. Ao adicionar um “=” no final do option (por exemplo, –queue=), podemos fornecer valores para o argumento option, tratando-o como um argumento normal, e recuperá-lo usando o método $this->option().

    protected $signature = 'app:send-notification {subject} {body} {--log}';

    public function handle()
    {
        $subject = $this->argument('subject');
        $body = $this->argument('body');
        User::all()
             ->each(function ($user) use($subject, $body) {
                if($this->option('log')) {
                    $this->info('Enviando email para '. $user->email);
                }
                $user->notify(new EmailNotification(
                    subject: $subject,
                    body: str_replace( '%name%', $user->name, $body)
                ));
             });
    }

Se necessário, é possível adicionar descrições aos argumentos do comando usando dois pontos “:” entre o nome do argumento e a sua descrição. É importante observar que deve haver um espaço entre os dois pontos, o nome e a descrição do argumento do comando.

app:send-notification {subject : Assunto da Mensagem} {body : O texto da mensagem} {--log : Exibe os emails enviados}
Exemplo de como as descrições dos argumentos são exibidos
Exemplo de como as descrições dos argumentos são exibidos

Pedindo argumentos obrigatórios

Quando lidamos com argumentos obrigatórios, podemos implementar a interface PromptsForMissingInput. Dessa forma, se o usuário não fornecer os argumentos ao chamar o comando, será solicitado a inserir os valores necessários.

class SendNotification extends Command implements PromptsForMissingInput
{
 //...
}
Prompting para argumento não informado
Prompting para argumento não informado

Perguntando para o usuário

Em vez de receber valores por meio de argumentos, podemos interagir com o usuário fazendo perguntas e aguardando uma resposta. A classe de comando oferece diversos métodos para essa finalidade, sendo o mais simples o $this->ask(<Texto explicativo>), que exibe uma mensagem e espera que o usuário insira um texto. Opcionalmente, podemos fornecer um valor padrão, que será utilizado caso o usuário não forneça nenhuma entrada.

$subject = $this->ask('Informe o assunto da mensagem: ');

// ou 

$subject = $this->ask('Informe o assunto da mensagem: ', 'Mensagem de Teste');

Se for necessário solicitar ao usuário a inserção de informações sensíveis que não devem ser exibidas na tela, podemos utilizar o método $this->secret(<mensagem>). Esse método opera de maneira semelhante ao ask, porém não exibe na tela o que o usuário está digitando.

Para questionamentos de confirmação, como o clássico “Deseja continuar?”, empregamos o método $this->confirm(<mensagem>), que retorna um valor booleano. Como segundo parâmetro, podemos fornecer um booleano para estabelecer o valor padrão, caso o usuário não forneça nenhuma resposta.

if( $this->confirm('Deseja continuar?') ){
    $this->info('continuando');
}else{
    $this->error('cancelando');
}

Podemos ter o recurso de auto-completar utilizando o método $this->anticipate(). Nesse caso, fornecemos a mensagem a ser exibida e um array contendo os valores a serem auto-completados.

$users = User::all()->pluck('name');
$name = $this->anticipate('Para qual usuario deseja enviar?', $users);

Para apresentar uma lista de opções para que o usuário escolha, utilizamos o método $this->choice(<mensagem>, <opções>, <índice selecionado por padrão>). No primeiro argumento, inserimos a mensagem a ser exibida; no segundo, um array contendo as opções disponíveis; e no terceiro, o índice no array do elemento que será o valor padrão caso o usuário não informe nenhum valor.

$users = User::all()->pluck('email');
$email = $this->choice(
    'Escolha para quem enviar a notificação?',
    $users->toArray(),
    0
);
$this->info($email);

Escrevendo no console

Para exibir dados no console, dispomos dos seguintes métodos: line(), info(), comment(), question(), warn() e error(). Cada um deles possui a cor apropriada para o seu propósito. Além disso, temos o método $this->newline(), que insere uma ou mais linhas em branco, dependendo do parâmetro fornecido.

$this->line("Iniciando envio de e-mails.");
$this->info("O e-mail foi enviado.");
$this->comment("Um comentário.");
$this->question("Pergunta?");
$this->newline(2);
$this->error("Ocorreu um erro ao enviar o e-mail.");
Exemplos de saida dos métodos line, info, comment, question e error.
Exemplos de saida dos métodos line(), info(), comment(), question() e error().

Podemos apresentar os dados em forma de tabela de maneira bastante simples utilizando o método $this->table(). Este método recebe um array com o cabeçalho (título das colunas) da tabela e uma matriz contendo os dados a serem exibidos.

$users = User::all(['name', 'email'])->toArray();
$this->table(['Nome', 'Email'], $users);
Exemplo de saída de console em forma de tabela.
Exemplo de saída de console em forma de tabela.

Outro componente para exibição de dados é a barra de progresso, especialmente útil quando lidamos com tarefas demoradas e desejamos informar sobre o progresso. Para isso, criamos um objeto ProgressBar utilizando o método $this->output->createProgressBar(), fornecendo o número total de “passos” que a barra de progresso deve ter. Em seguida, chamamos o método advance() para avançar um passo na barra de progresso.

$users = User::all();
$bar = $this->output->createProgressBar(count($users));
$users->each(function ($user) use($subject, $body, $bar) {
    $user->notify(new EmailNotification(
        subject: $subject,
        body: str_replace( '%name%', $user->name, $body)
    ));
    $bar->advance();
});
Barra de Progresso do Laravel Console
Barra de Progresso do Laravel Console

Chamando comandos programaticamente

Também é possível chamar comandos através de um controller ou service utilizando a Facade Artisan. Isso é feito invocando o método call(), onde o primeiro parâmetro é o comando a ser executado, e o segundo é um array contendo os argumentos do comando.

Artisan::call('app:send-notification', ['subject' => 'titulo', 'body' => 'corpo do email']);

Bem, esta foi uma breve introdução ao Artisan Laravel, um recurso muito interessante do framework que possibilita a automação de diversas tarefas nos nossos sistemas. Esses comandos podem ser facilmente integrados ao cron do sistema para execução de tarefas agendadas. Espero que tenha sido de alguma ajuda. T++.