404 Not Found

404 Not Found


nginx

O Console Artisan do Laravel e Comandos Personalizados

Artisan é o "canivete suíço" do Laravel — com mais de 100 comandos integrados cobrindo todas as operações e tarefas de manutenção, e comandos personalizados que permitem lidar com tarefas repetitivas com um único clique.

1. O Que Você Vai Aprender


2. Uma História Real de um Engenheiro de Operações

(1) Dor: Executar manualmente 20 tarefas de operações todos os dias

Toda manhã, Charlie tem que executar manualmente as seguintes tarefas: limpar sessões expiradas, gerar relatórios diários, sincronizar status de assinaturas, enviar emails de lembrete de expiração, fazer backup do banco de dados... Vinte tarefas espalhadas por cinco janelas de terminal — se ele esquecer uma, terá que pagar do bolso. Bob perguntou: "Essas não são todas tarefas agendadas? Por que você precisa executá-las manualmente?"

(2) Soluções com Comandos Artisan e Agendamento

Comandos Artisan personalizados envolvem tarefas repetitivas em um único comando, e o Schedule executa-as automaticamente conforme agendado — Charlie só precisa dar uma olhada no log de execução uma vez por dia.

BASH
# Um comando faz tudo
php artisan shopmetrics:daily-maintenance

# Ou deixe o agendador executar automaticamente às 2 da manhã
php artisan schedule:run

(3) Resultado

Depois que Charlie configurou o agendamento com Artisan, todas as 20 tarefas executaram automaticamente, e a taxa de falhas caiu de 5% para 0%.


3. Lista Completa de Comandos Integrados

(1) Categorias de Comandos Comuns

Categoria Comando Descrição
Aplicação about Visão Geral de Informações do Ambiente
down / up Alternar Modo de Manutenção
env Exibir configuração .env
Banco de Dados migrate Executar Migration
migrate:rollback Reverter Migration
migrate:fresh Reconstruir Banco de Dados
db:seed Preenchimento de Dados
db:show Informações do Banco de Dados
Cache cache:clear Limpar Cache
config:cache / clear Cache de Configuração
route:cache / clear Cache de Rotas
view:cache / clear Cache de Views
Rotas route:list Listar todas as rotas
Fila queue:work Iniciar Worker
queue:failed Ver Tarefas Falhadas
queue:retry Retentar tarefas falhadas
Gerar make:model Criar Model
make:controller Criar Controller
make:migration Criar Migration
make:command Criar Command

(1) ▶ Exemplo: Referência Rápida de Comandos Artisan Comuns

BASH
# Informações da app
php artisan about
php artisan env

# Operações de banco de dados
php artisan migrate --seed
php artisan db:show --counts

# Gerenciamento de cache
php artisan cache:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize           # Cache config + route + view

# Modo de manutenção
php artisan down --secret="maintenance-token"  # Permitir acesso com ?secret=
php artisan up

# Gerenciamento de fila
php artisan queue:work --queue=high,default
php artisan queue:failed
php artisan queue:retry all

# Inspeção de rotas
php artisan route:list --path=api
php artisan route:list --columns=method,uri,name

Saída:

TEXT
# Comando executado com sucesso

4. Comandos Personalizados

(1) Criar Comando

BASH
php artisan make:command ProcessTenantAnalytics
# Cria: app/Console/Commands/ProcessTenantAnalytics.php

(2) Sintaxe Signature do Comando

PHP
// app/Console/Commands/ProcessTenantAnalytics.php
class ProcessTenantAnalytics extends Command
{
    // Sintaxe Signature: {argument} {--option}
    protected $signature = 'shopmetrics:analytics
                            {tenant? : ID ou slug do Tenant (opcional)}
                            {--type=monthly : Tipo de relatório (daily|weekly|monthly)}
                            {--force : Forçar recálculo}
                            {--format=csv : Formato de saída (csv|json)}';

    protected $description = 'Processar analytics para tenants';

    public function handle(): int
    {
        $tenant = $this->argument('tenant');
        $type = $this->option('type');
        $force = $this->option('force');

        if ($tenant) {
            $this->processSingleTenant($tenant, $type, $force);
        } else {
            $this->processAllTenants($type, $force);
        }

        return self::SUCCESS;
    }
}

(3) Regras da Sintaxe Signature

Sintaxe Descrição Exemplo
{name} Parâmetro obrigatório {tenant}
{name?} Parâmetros opcionais {tenant?}
{name=default} Com valor padrão {type=monthly}
{--option} Opção Booleana --force
{--option=default} Opções com Valores --format=csv
{--O|shortcut} Opções curtas --force|f

(1) ▶ Exemplo: Comandos de Gerenciamento de Tenants do ShopMetrics

PHP
// app/Console/Commands/ManageTenant.php
class ManageTenant extends Command
{
    protected $signature = 'shopmetrics:tenant
                            {action : Ação a executar (list|suspend|activate|stats)}
                            {tenant? : ID ou slug do Tenant}
                            {--with-users : Incluir estatísticas de usuários}';

    protected $description = 'Gerenciar tenants do ShopMetrics';

    public function handle(): int
    {
        $action = $this->argument('action');

        match ($action) {
            'list' => $this->listTenants(),
            'suspend' => $this->suspendTenant(),
            'activate' => $this->activateTenant(),
            'stats' => $this->showTenantStats(),
            default => $this->error("Ação desconhecida: {$action}"),
        };

        return self::SUCCESS;
    }

    private function listTenants(): void
    {
        $tenants = Tenant::withCount(['shops', 'users'])->get();
        $this->table(
            ['ID', 'Nome', 'Slug', 'Status', 'Lojas', 'Usuários'],
            $tenants->map(fn ($t) => [
                $t->id, $t->name, $t->slug, $t->status,
                $t->shops_count, $t->users_count,
            ])
        );
    }

    private function suspendTenant(): void
    {
        $identifier = $this->argument('tenant') ?? $this->ask('Digite o ID ou slug do tenant:');
        $tenant = $this->resolveTenant($identifier);
        $tenant->update(['status' => 'suspended']);
        $this->info("Tenant {$tenant->name} foi suspenso.");
    }

    private function resolveTenant(string $identifier): Tenant
    {
        return is_numeric($identifier)
            ? Tenant::findOrFail($identifier)
            : Tenant::whereSlug($identifier)->firstOrFail();
    }
}

Saída:

TEXT
// Execução bem-sucedida

5. Argumentos e Opções de Comando

(1) Recuperar Parâmetros

PHP
// Argumentos
$name = $this->argument('name');        // Argumento único
$all = $this->arguments();              // Todos os argumentos como array

// Opções
$force = $this->option('force');        // Opção única (booleano ou valor)
$all = $this->options();                // Todas as opções como array

(2) Parâmetros de Array

PHP
// Signature com argumento de array
protected $signature = 'shopmetrics:report
                        {tenants* : Um ou mais IDs de tenant}
                        {--type=monthly}';

// Uso
php artisan shopmetrics:report 1 2 3 --type=weekly

// Acesso
$tenants = $this->argument('tenants'); // [1, 2, 3]

(3) Métodos de Saída

Método Descrição Saída de Exemplo
info() Informação Verde ✓ Concluído
error() Erro Vermelho ✗ Falhou
warn() Aviso Amarelo ⚠ Aviso
line() Texto simples Texto simples
table() Tabela Tabela Formatada
progressBar() Barra de Progresso ██████░░ 60%

(1) ▶ Exemplo: Comando de limpeza de dados do ShopMetrics com barra de progresso

PHP
// app/Console/Commands/CleanupExpiredData.php
class CleanupExpiredData extends Command
{
    protected $signature = 'shopmetrics:cleanup
                            {--days=90 : Excluir dados mais antigos que N dias}
                            {--dry-run : Mostrar o que seria excluído}';

    protected $description = 'Limpar dados expirados (relatórios antigos, tokens expirados)';

    public function handle(): int
    {
        $days = $this->option('days');
        $dryRun = $this->option('dry-run');
        $cutoff = now()->subDays($days);

        // Limpar tokens expirados
        $expiredTokens = Sanctum::$personalAccessTokenModel::where('last_used_at', '<', $cutoff);
        $this->info(($dryRun ? 'Excluiria' : 'Excluindo') . " {$expiredTokens->count()} tokens expirados.");

        // Limpar relatórios antigos do S3
        $bar = $this->output->createProgressBar(Tenant::count());
        $deletedFiles = 0;

        Tenant::chunk(100, function ($tenants) use ($cutoff, $dryRun, &$deletedFiles, $bar) {
            foreach ($tenants as $tenant) {
                $files = Storage::disk('s3')->allFiles("reports/{$tenant->slug}");
                foreach ($files as $file) {
                    if (Storage::disk('s3')->lastModified($file) < $cutoff->timestamp) {
                        if (!$dryRun) Storage::disk('s3')->delete($file);
                        $deletedFiles++;
                    }
                }
                $bar->advance();
            }
        });

        $bar->finish();
        $this->newLine();
        $this->info(($dryRun ? 'Excluiria' : 'Excluídos') . " {$deletedFiles} arquivos de relatório antigos.");

        if (!$dryRun) {
            $expiredTokens->delete();
        }

        return self::SUCCESS;
    }
}

Saída:

TEXT
// Execução bem-sucedida

6. Agendamento de Comandos

(1) Definição de Agendamento

PHP
// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('shopmetrics:analytics --type=daily')
    ->dailyAt('02:00')
    ->onOneServer()
    ->withoutOverlapping()
    ->emailOutputOnFailure('admin@shopmetrics.io');

Schedule::command('shopmetrics:cleanup --days=90')
    ->weekly()
    ->sundays()
    ->at('03:00')
    ->onOneServer();

Schedule::command('shopmetrics:sync-subscriptions')
    ->dailyAt('06:00')
    ->onOneServer()
    ->withoutOverlapping();

Schedule::job(new ProcessMonthlyAnalyticsJob)
    ->monthlyOn(1, '00:00')
    ->onOneServer();

(2) Opções de Frequência de Agendamento

Método Frequência Cron Equivalente
everyMinute() Por Minuto * * * * *
everyFiveMinutes() A cada 5 minutos */5 * * * *
hourly() Por hora 0 * * * *
daily() Meia-noite todos os dias 0 0 * * *
dailyAt('14:00') 14:00 diariamente 0 14 * * *
weekly() Todo domingo à meia-noite 0 0 * * 0
monthly() Meia-noite no dia 1 de cada mês 0 0 1 * *
cron('...') Cron Personalizado Qualquer

(3) Restrições de Agendamento

Método Descrição
onOneServer() Executar em apenas um servidor
withoutOverlapping() Execução sobreposta não é permitida
runInBackground() Executar em segundo plano
when(Closure) Execução Condicional
environments('prod') Ambiente Especificado
emailOutputOnFailure() Notificação por email em caso de falha

(1) ▶ Exemplo: Configuração Completa de Agendamento do ShopMetrics

PHP
// routes/console.php
Schedule::command('shopmetrics:analytics --type=daily')
    ->dailyAt('02:00')
    ->onOneServer()
    ->withoutOverlapping(60)
    ->emailOutputOnFailure('ops@shopmetrics.io');

Schedule::command('shopmetrics:cleanup --days=90')
    ->weeklyOn(Schedule::SUNDAY, '03:00')
    ->onOneServer();

Schedule::command('shopmetrics:send-expiring-notifications')
    ->dailyAt('08:00')
    ->when(fn () => Subscription::expiringSoon()->exists());

Schedule::command('queue:prune-failed --hours=168')
    ->daily();

Schedule::command('queue:prune-batches --hours=168')
    ->daily();

// Entrada Cron no servidor
// * * * * * cd /var/www/shopmetrics && php artisan schedule:run >> /dev/null 2>&1

Saída:

TEXT
// Execução bem-sucedida

7. Comandos Interativos

(1) Métodos de Interação

PHP
// Pedir entrada
$name = $this->ask('Qual é o nome do tenant?');

// Pedir com padrão
$email = $this->ask('Endereço de email?', 'admin@example.com');

// Entrada secreta (senhas)
$password = $this->secret('Digite a senha:');

// Confirmar (sim/não)
if ($this->confirm('Deseja continuar?', true)) {
    // Padrão: sim
}

// Escolha (seleção única)
$type = $this->choice(
    'Selecione o tipo de relatório',
    ['daily', 'weekly', 'monthly'],
    0 // índice padrão
);

// Antecipar (autocompletar)
$name = $this->anticipate('Nome do tenant', Tenant::pluck('name')->toArray());

(2) Fluxo de Trabalho Interativo Compreensivo

PHP
// app/Console/Commands/SetupTenant.php
class SetupTenant extends Command
{
    protected $signature = 'shopmetrics:tenant-setup';

    protected $description = 'Assistente interativo de configuração de tenant';

    public function handle(): int
    {
        $this->info('=== Assistente de Configuração de Tenant ShopMetrics ===');

        $name = $this->ask('Nome do tenant');
        $slug = $this->anticipate('Slug', [Str::slug($name)]);
        $domain = $this->ask('Domínio personalizado (opcional)', $slug . '.shopmetrics.io');
        $plan = $this->choice('Selecione o plano', ['Starter', 'Pro', 'Enterprise'], 1);
        $ownerEmail = $this->ask('Email do proprietário');

        $this->table(
            ['Campo', 'Valor'],
            [['Nome', $name], ['Slug', $slug], ['Domínio', $domain], ['Plano', $plan], ['Proprietário', $ownerEmail]],
        );

        if (!$this->confirm('Criar este tenant?', true)) {
            $this->warn('Cancelado.');
            return self::FAILURE;
        }

        $tenant = Tenant::create(compact('name', 'slug', 'domain'));
        User::factory()->create([
            'tenant_id' => $tenant->id,
            'email' => $ownerEmail,
            'role' => 'tenant_owner',
        ]);

        $this->info("Tenant {$name} criado com sucesso!");
        return self::SUCCESS;
    }
}

(1) ▶ Exemplo: Comando Interativo de Exportação de Dados do ShopMetrics

PHP
// app/Console/Commands/ExportData.php
class ExportData extends Command
{
    protected $signature = 'shopmetrics:export';

    protected $description = 'Ferramenta interativa de exportação de dados';

    public function handle(): int
    {
        $type = $this->choice('O que exportar?', [
            'orders' => 'Pedidos',
            'products' => 'Produtos',
            'analytics' => 'Relatório de Analytics',
        ]);

        $tenant = $this->anticipate('Tenant (deixe em branco para todos)', Tenant::pluck('name')->push('Todos')->toArray());

        $format = $this->choice('Formato?', ['csv', 'xlsx', 'json'], 0);

        $dateFrom = $this->ask('Data de (Y-m-d, opcional)');
        $dateTo = $this->ask('Data até (Y-m-d, opcional)');

        $this->info("Exportando {$type} para {$tenant} no formato {$format}...");

        $query = match ($type) {
            'orders' => Order::query(),
            'products' => Product::query(),
            'analytics' => AnalyticsReport::query(),
        };

        if ($tenant !== 'Todos') {
            $tenantModel = Tenant::whereName($tenant)->firstOrFail();
            $query->where('tenant_id', $tenantModel->id);
        }

        if ($dateFrom) $query->where('created_at', '>=', $dateFrom);
        if ($dateTo) $query->where('created_at', '<=', $dateTo);

        $count = $query->count();
        $this->info("Encontrados {$count} registros.");

        if (!$this->confirm("Exportar {$count} registros?", true)) {
            return self::FAILURE;
        }

        $path = ExportService::export($query, $format);
        $this->info("Exportação salva em: {$path}");

        return self::SUCCESS;
    }
}

Saída:

TEXT
// Execução bem-sucedida

8. Exemplo Compreensivo: Conjunto de Comandos de Operações do ShopMetrics

PHP
// ============================================
// Compreensivo: Comandos de Operações do ShopMetrics
// Abrange: signature, options, progress, scheduling, interactive
// ============================================

// app/Console/Commands/DailyMaintenance.php
class DailyMaintenance extends Command
{
    protected $signature = 'shopmetrics:daily-maintenance
                            {--skip-analytics : Pular processamento de analytics}
                            {--skip-cleanup : Pular limpeza de dados}
                            {--notify : Enviar notificação de conclusão}';

    protected $description = 'Executar tarefas de manutenção diária';

    public function handle(): int
    {
        $this->info('Iniciando manutenção diária...');

        if (!$this->option('skip-analytics')) {
            $this->processAnalytics();
        }

        if (!$this->option('skip-cleanup')) {
            $this->cleanupExpiredData();
        }

        $this->syncSubscriptions();

        if ($this->option('notify')) {
            $this->sendCompletionNotification();
        }

        $this->info('Manutenção diária concluída.');
        return self::SUCCESS;
    }

    private function processAnalytics(): void
    {
        $this->info('Processando analytics diários...');
        $tenants = Tenant::active()->get();
        $bar = $this->output->createProgressBar($tenants->count());

        foreach ($tenants as $tenant) {
            GenerateReportJob::dispatch($tenant, 'daily', 'json');
            $bar->advance();
        }

        $bar->finish();
        $this->newLine();
    }

    private function cleanupExpiredData(): void
    {
        $this->call('shopmetrics:cleanup', ['--days' => 90]);
    }

    private function syncSubscriptions(): void
    {
        $this->call('shopmetrics:sync-subscriptions');
    }

    private function sendCompletionNotification(): void
    {
        $admin = User::where('role', 'super_admin')->first();
        $admin?->notify(new DailyMaintenanceCompleted());
    }
}

// Agendar
// routes/console.php
Schedule::command('shopmetrics:daily-maintenance --notify')
    ->dailyAt('02:00')
    ->onOneServer()
    ->withoutOverlapping()
    ->emailOutputOnFailure('ops@shopmetrics.io');

❓ Perguntas Frequentes

P Qual é a diferença entre make:command e make:command --command?
R --command=xxx Especifica um nome de comando personalizado: php artisan make:command SendEmails --command=emails:send. Se omitido, o nome padrão app:console-commands:send-emails é usado. É recomendado sempre especificar um nome de comando personalizado.
P Devo usar Cron ou Laravel Schedule para agendamento de tarefas?
R Use Laravel Schedule. O servidor precisa apenas de um job Cron: * * * * * php artisan schedule:run. Todas as tarefas são definidas no código, tornando-as versionáveis e testáveis. Não configure um job Cron separado para cada tarefa.
P Quando devo usar onOneServer?
R Ao implantar em múltiplos servidores, para evitar que a mesma tarefa agendada execute simultaneamente em múltiplas máquinas. É necessário um driver de cache Redis ou database para coordenação. Não é necessário em uma configuração de servidor único.
P Como testar comandos personalizados?
R Use $this->artisan('command:name', ['arg' => 'value']) para executar o comando em um teste e verifique que a saída é ->expectsOutput('Done') ou verifique as mudanças no banco de dados.
P O que devo fazer se os comandos forem muito lentos?
R Processe operações demoradas de forma assíncrona usando uma fila; comandos são responsáveis apenas por fazer dispatch de jobs. Para grandes conjuntos de dados, use chunking com uma barra de progresso. Adicione a opção --dry-run para pré-visualizar os resultados antes da execução.
P Como registrar a saída de comandos?
R Use ->appendOutputTo(storage_path('logs/command.log')) para anexar logs no agendador; use Log::info() dentro do comando; ou use emailOutputOnFailure() para enviar uma notificação por email em caso de falha.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie um comando chamado shopmetrics:tenant-stats que aceita um parâmetro tenant e exibe o número de lojas, pedidos e receita total desse tenant, formatado usando o comando table.

  2. Exercício Avançado (⭐⭐): Crie o comando shopmetrics:daily-maintenance, que inclui processamento de analytics (com barra de progresso) + limpeza de dados expirados + sincronização de assinaturas, e configure o Schedule para executar automaticamente às 2:00 da manhã todos os dias.

  3. Desafio (⭐⭐⭐): Implemente um comando assistente interativo shopmetrics:tenant-setup — insira o nome do tenant, slug, plano e endereço de email do administrador sequencialmente, com validação em cada etapa, e finalmente confirme a criação. Inclua sugestões de autocompletar (anticipate) para o nome do tenant.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%