¿Qué es Artisan?
Artisan es la herramienta de línea de comandos (CLI) incluida en Laravel.
Existe como script artisan en la raíz del proyecto y ofrece numerosos comandos para agilizar el desarrollo y la operación.
Para ver la lista de comandos disponibles, usa list.
Para consultar el uso de un comando, antepón help.
Si utilizas Laravel Sail, usa sail artisan en lugar de php artisan.
Los comandos se ejecutan dentro del contenedor Docker../vendor/bin/sail artisan list
Tinker
Laravel Tinker es un REPL que te permite interactuar en la línea de comandos con toda la aplicación: modelos Eloquent, jobs, eventos, etc.
Una vez dentro puedes ejecutar código PHP directamente.
// Obtener y comprobar un usuario
>>> App\Models\User::find(1)
// Crear un registro con una factory
>>> App\Models\User::factory()->create()
// Invocar directamente una clase de servicio
>>> app(App\Services\OrderService::class)->process(1)
Tinker modifica realmente los datos. Ten cuidado al ejecutarlo en producción.
Es apropiado para pruebas y depuración en local o staging.
Crear comandos personalizados
Generar el comando
Con make:command generas el esqueleto de una clase de comando.
php artisan make:command ImportProducts
Se crea app/Console/Commands/ImportProducts.php.
Estructura básica
La clase generada tiene tres elementos principales: $signature, $description y handle().
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class ImportProducts extends Command
{
/**
* Nombre del comando y definición de argumentos/opciones
*/
protected $signature = 'import:products';
/**
* Descripción del comando (se muestra en php artisan list)
*/
protected $description = 'Importa datos de productos desde un CSV';
/**
* Lógica de ejecución del comando
*/
public function handle(): void
{
$this->info('Comenzando la importación...');
// Escribe aquí el procesamiento
$this->info('Terminado.');
}
}
En Laravel 13 también puedes usar atributos PHP.use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
#[Signature('import:products')]
#[Description('Importa datos de productos desde un CSV')]
class ImportProducts extends Command
{
public function handle(): void
{
// ...
}
}
Definición de argumentos y opciones
Define el nombre, argumentos y opciones del comando en $signature.
protected $signature = 'import:products
{file : Ruta del CSV a importar}
{--limit= : Número máximo de filas a importar}
{--dry-run : No guarda realmente en la BD (solo para comprobar)}';
| Notación | Tipo | Descripción |
|---|
{file} | Argumento obligatorio | Si se omite, es un error |
{file?} | Argumento opcional | Si se omite, null |
{file=products.csv} | Argumento con valor por defecto | Se usa el valor por defecto si se omite |
{--queue} | Opción tipo flag | true si se indica, false si no |
{--limit=} | Opción con valor | Pasa el valor así: --limit=100 |
{--limit=50} | Opción con valor por defecto | Si se omite, 50 |
{--Q|queue} | Opción con atajo | También se puede indicar con -Q |
Obtener argumentos y opciones
Usa argument() y option() dentro de handle().
public function handle(): void
{
$file = $this->argument('file'); // Obtener argumento
$limit = $this->option('limit'); // Obtener opción
$dryRun = $this->option('dry-run'); // Opción flag (bool)
$this->info("Archivo: {$file}");
if ($dryRun) {
$this->warn('Modo dry-run: se omitirá el guardado en la BD');
}
}
Con input() puedes obtener los argumentos y opciones del comando mediante accesores tipados, igual que en una HTTP request.
use App\Enums\ReportType;
public function handle(): void
{
// Obtiene argumentos/opciones como instancia CommandInput
$from = $this->input()->date('from');
$type = $this->input()->enum('type', ReportType::class);
$limit = $this->input()->integer('limit');
}
Si solo quieres uno con nombre, pásaselo directamente (busca en argumentos y opciones).
$queue = $this->input('queue', 'default');
argument() y option() devuelven strings; los accesores tipados de input() convierten al tipo adecuado. Muy útil para manejar de forma segura fechas, números o Enums.
Interacción con el usuario
Mensajes por pantalla
Existen métodos para imprimir mensajes con colores.
$this->info('El proceso terminó correctamente'); // Verde
$this->warn('Esta operación no se puede deshacer'); // Amarillo
$this->error('Se ha producido un error'); // Rojo
$this->line('Texto normal'); // Sin color
$this->comment('Información adicional'); // Gris claro
Preguntar al usuario
Puedes recibir entradas del usuario de forma interactiva.
// Entrada de texto
$name = $this->ask('Introduce el nombre del responsable');
// Con valor por defecto
$env = $this->ask('Selecciona el entorno', 'production');
// Información sensible (no se muestra al escribir)
$password = $this->secret('Introduce la clave de API');
// Confirmación Sí/No
if (! $this->confirm('¿Importar a la BD de producción?')) {
$this->info('Cancelado.');
return;
}
// Elegir de entre opciones
$format = $this->choice('Elige el formato de salida', ['csv', 'json', 'xml'], 0);
Barra de progreso
Muestra fácilmente el progreso al procesar grandes cantidades de datos.
use App\Models\Product;
// Al pasar una colección, se muestra la barra automáticamente
$this->withProgressBar(Product::cursor(), function (Product $product) {
$this->processProduct($product);
});
Para un control manual más fino:
$total = Product::count();
$bar = $this->output->createProgressBar($total);
$bar->start();
Product::cursor()->each(function (Product $product) use ($bar) {
$this->processProduct($product);
$bar->advance();
});
$bar->finish();
$this->newLine();
Casos prácticos
Comando de importación de datos
Ejemplo de comando que importa productos desde un CSV.
Generar el comando
php artisan make:command ImportProducts
Implementar el comando
<?php
namespace App\Console\Commands;
use App\Models\Product;
use Illuminate\Console\Command;
class ImportProducts extends Command
{
protected $signature = 'import:products
{file : Ruta del CSV}
{--limit= : Número máximo de filas a importar}
{--dry-run : Solo comprobar (no guarda en la BD)}';
protected $description = 'Importa datos de productos desde un CSV';
public function handle(): void
{
$filePath = $this->argument('file');
$limit = $this->option('limit') ? (int) $this->option('limit') : null;
$dryRun = $this->option('dry-run');
if (! file_exists($filePath)) {
$this->error("No se encuentra el archivo: {$filePath}");
return;
}
// Lee el CSV con funciones nativas de PHP
$handle = fopen($filePath, 'r');
$headers = fgetcsv($handle); // Primera línea como cabeceras
$rows = [];
while (($row = fgetcsv($handle)) !== false) {
$rows[] = array_combine($headers, $row);
if ($limit !== null && count($rows) >= $limit) {
break;
}
}
fclose($handle);
$count = 0;
$this->withProgressBar($rows, function (array $row) use ($dryRun, &$count) {
if (! $dryRun) {
Product::updateOrCreate(
['sku' => $row['sku']],
[
'name' => $row['name'],
'price' => $row['price'],
]
);
}
$count++;
});
$this->newLine();
if ($dryRun) {
$this->warn("{$count} filas encontradas (dry-run: no se ha guardado nada)");
} else {
$this->info("Importación completada: {$count} filas.");
}
}
}
Ejecutar el comando
# Importación normal
php artisan import:products storage/products.csv
# Limitar el número de filas
php artisan import:products storage/products.csv --limit=100
# Comprobar en dry-run
php artisan import:products storage/products.csv --dry-run
Comando de mantenimiento periódico
Ejemplo para eliminar datos antiguos periódicamente.
<?php
namespace App\Console\Commands;
use App\Models\Order;
use Illuminate\Console\Command;
class PruneOldOrders extends Command
{
protected $signature = 'orders:prune {--days=90 : Antigüedad mínima de los datos a eliminar}';
protected $description = 'Elimina los pedidos completados con más antigüedad que la indicada';
public function handle(): void
{
$days = (int) $this->option('days');
if (! $this->confirm("¿Eliminar pedidos completados con más de {$days} días?")) {
$this->info('Cancelado.');
return;
}
$deleted = Order::where('status', 'completed')
->where('created_at', '<', now()->subDays($days))
->delete();
$this->info("{$deleted} pedidos eliminados.");
}
}
Combinación con la programación de tareas
Puedes ejecutar tus comandos con la programación de tareas en routes/console.php.
Consulta Programación de tareas para más detalles.
use Illuminate\Support\Facades\Schedule;
// Diario a las 2 de la madrugada
Schedule::command('orders:prune --days=90')->dailyAt('2:00');
// Cada lunes a las 9
Schedule::command('import:products storage/weekly.csv')->weeklyOn(1, '9:00');
Pruebas de comandos
Usa las utilidades de prueba de Laravel para verificar el funcionamiento de los comandos Artisan.
<?php
namespace Tests\Feature\Commands;
use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PruneOldOrdersTest extends TestCase
{
use RefreshDatabase;
public function test_it_deletes_old_completed_orders(): void
{
// Crea un pedido completado hace más de 90 días
Order::factory()->create([
'status' => 'completed',
'created_at' => now()->subDays(100),
]);
// Pedido reciente (no debería eliminarse)
Order::factory()->create([
'status' => 'completed',
'created_at' => now()->subDays(10),
]);
$this->artisan('orders:prune', ['--days' => 90])
->expectsConfirmation('¿Eliminar pedidos completados con más de 90 días?', 'yes')
->expectsOutput('1 pedidos eliminados.')
->assertExitCode(0);
$this->assertDatabaseCount('orders', 1);
}
public function test_it_cancels_when_user_declines(): void
{
Order::factory()->create(['status' => 'completed', 'created_at' => now()->subDays(100)]);
$this->artisan('orders:prune', ['--days' => 90])
->expectsConfirmation('¿Eliminar pedidos completados con más de 90 días?', 'no')
->expectsOutput('Cancelado.')
->assertExitCode(0);
$this->assertDatabaseCount('orders', 1);
}
}
Ejemplos de aserciones disponibles con artisan():| Método | Descripción |
|---|
expectsOutput('...') | Comprueba que se imprime el texto indicado |
expectsQuestion('?', 'respuesta') | Simula la entrada de ask() |
expectsConfirmation('?', 'yes') | Simula la respuesta de confirm() |
expectsChoice('?', 'opción') | Simula la elección de choice() |
assertExitCode(0) | Verifica el código de salida (0 = éxito) |
assertFailed() | Verifica que el código de salida es distinto de 0 |
Comandos habituales
# Crear un comando nuevo
php artisan make:command CommandName
# Lista de comandos disponibles
php artisan list
# Ayuda de un comando
php artisan help command:name
# Iniciar Tinker
php artisan tinker