Laravel Routing: All You Need to Know About GET, POST, and Resource Routes
Master Laravel's powerful routing system for handling web requests.
While Laravel comes with many useful built-in Artisan commands, the real power comes when you create your own. Custom Artisan commands allow you to automate repetitive tasks, perform batch operations, and create powerful CLI tools for your application.
Why Create Custom Commands?
Let's start with a simple example: a welcome command that greets users.
php artisan make:command WelcomeCommand
This creates app/Console/Commands/WelcomeCommand.php:
<?php
// app/Console/Commands/WelcomeCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class WelcomeCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:welcome-command';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Welcome to Laravel Artisan Commands!');
}
}
Commands are automatically registered if they're in the app/Console/Commands directory and your console kernel is properly configured.
<?php
// app/Console/Kernel.php
namespace App\Console;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}
Now run your command:
php artisan app:welcome-command
<?php
// app/Console/Commands/GreetUserCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class GreetUserCommand extends Command
{
protected $signature = 'greet:user {name}';
protected $description = 'Greet a user by name';
public function handle()
{
$name = $this->argument('name');
$this->info("Hello, {$name}! Welcome to our application.");
}
}
Usage:
php artisan greet:user John
protected $signature = 'greet:user
{name=World : The name of the user to greet}
{--formal : Use formal greeting}';
public function handle()
{
$name = $this->argument('name');
$formal = $this->option('formal');
$greeting = $formal ? "Good day, {$name}." : "Hello, {$name}!";
$this->info($greeting);
}
Usage:
php artisan greet:user John --formal
php artisan greet:user # Uses "World" as default
protected $signature = 'email:send
{emails* : List of email addresses}
{--subject=Newsletter : Email subject}
{--queue : Whether to queue the emails}';
public function handle()
{
$emails = $this->argument('emails');
$subject = $this->option('subject');
$shouldQueue = $this->option('queue');
$this->info("Sending '{$subject}' to: " . implode(', ', $emails));
if ($shouldQueue) {
$this->info('Emails will be queued for sending.');
}
}
Usage:
php artisan email:send user1@example.com user2@example.com --subject="Welcome" --queue
<?php
// app/Console/Commands/DatabaseMaintenanceCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use App\Models\Log;
use App\Models\TempFile;
class DatabaseMaintenanceCommand extends Command
{
protected $signature = 'db:maintenance
{--days=30 : Clean records older than X days}
{--optimize : Optimize database tables}
{--backup : Create database backup}';
protected $description = 'Perform database maintenance tasks';
public function handle()
{
$this->info('Starting database maintenance...');
$this->cleanOldRecords();
$this->cleanTempFiles();
if ($this->option('optimize')) {
$this->optimizeTables();
}
if ($this->option('backup')) {
$this->createBackup();
}
$this->info('Database maintenance completed!');
}
protected function cleanOldRecords()
{
$days = $this->option('days');
$cutoffDate = now()->subDays($days);
$this->info("Cleaning records older than {$days} days...");
// Clean old logs
$logCount = Log::where('created_at', '<', $cutoffDate)->delete();
$this->info("Deleted {$logCount} old log records.");
// Clean old sessions
$sessionCount = DB::table('sessions')
->where('last_activity', '<', $cutoffDate->timestamp)
->delete();
$this->info("Deleted {$sessionCount} old session records.");
}
protected function cleanTempFiles()
{
$tempFileCount = TempFile::where('created_at', '<', now()->subDay())->delete();
$this->info("Deleted {$tempFileCount} temporary files.");
}
protected function optimizeTables()
{
$this->info('Optimizing database tables...');
$tables = DB::select('SHOW TABLES');
foreach ($tables as $table) {
$tableName = reset($table);
DB::statement("OPTIMIZE TABLE {$tableName}");
$this->info("Optimized table: {$tableName}");
}
}
protected function createBackup()
{
$this->info('Creating database backup...');
$filename = 'backup-' . date('Y-m-d-H-i-s') . '.sql';
$path = storage_path('app/backups/' . $filename);
// Simple backup using mysqldump (adjust for your database)
$command = sprintf(
'mysqldump -u%s -p%s %s > %s',
config('database.connections.mysql.username'),
config('database.connections.mysql.password'),
config('database.connections.mysql.database'),
$path
);
exec($command, $output, $returnVar);
if ($returnVar === 0) {
$this->info("Backup created: {$filename}");
} else {
$this->error('Backup failed!');
}
}
}
<?php
// app/Console/Commands/UserManagerCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class UserManagerCommand extends Command
{
protected $signature = 'user:manage
{action : create|list|delete|reset-password}
{--id= : User ID for specific actions}
{--name= : User name}
{--email= : User email}
{--role=user : User role}
{--password= : User password (auto-generated if empty)}';
protected $description = 'Manage application users';
public function handle()
{
$action = $this->argument('action');
switch ($action) {
case 'create':
$this->createUser();
break;
case 'list':
$this->listUsers();
break;
case 'delete':
$this->deleteUser();
break;
case 'reset-password':
$this->resetPassword();
break;
default:
$this->error("Invalid action: {$action}");
$this->info('Available actions: create, list, delete, reset-password');
}
}
protected function createUser()
{
$name = $this->option('name') ?? $this->ask('Enter user name:');
$email = $this->option('email') ?? $this->ask('Enter user email:');
$role = $this->option('role');
$password = $this->option('password') ?? Str::random(12);
if (User::where('email', $email)->exists()) {
$this->error("User with email {$email} already exists!");
return;
}
$user = User::create([
'name' => $name,
'email' => $email,
'password' => Hash::make($password),
'role' => $role,
]);
$this->info("User created successfully!");
$this->table(
['ID', 'Name', 'Email', 'Role', 'Password'],
[[$user->id, $user->name, $user->email, $user->role, $password]]
);
}
protected function listUsers()
{
$users = User::select('id', 'name', 'email', 'role', 'created_at')
->get()
->toArray();
$this->table(
['ID', 'Name', 'Email', 'Role', 'Created At'],
$users
);
$this->info("Total users: " . count($users));
}
protected function deleteUser()
{
$userId = $this->option('id') ?? $this->ask('Enter user ID to delete:');
$user = User::find($userId);
if (!$user) {
$this->error("User with ID {$userId} not found!");
return;
}
if ($this->confirm("Are you sure you want to delete user: {$user->name} ({$user->email})?")) {
$user->delete();
$this->info("User deleted successfully!");
}
}
protected function resetPassword()
{
$userId = $this->option('id') ?? $this->ask('Enter user ID:');
$newPassword = $this->option('password') ?? Str::random(12);
$user = User::find($userId);
if (!$user) {
$this->error("User with ID {$userId} not found!");
return;
}
$user->update([
'password' => Hash::make($newPassword)
]);
$this->info("Password reset successfully!");
$this->info("New password: {$newPassword}");
}
}
public function handle()
{
if ($this->confirm('Do you wish to continue?', true)) {
$this->info('Continuing with the operation...');
// Perform the operation
} else {
$this->info('Operation cancelled.');
}
}
public function handle()
{
$environment = $this->choice(
'Which environment are you deploying to?',
['local', 'staging', 'production'],
0
);
$this->info("Deploying to: {$environment}");
// Show progress bar for long operations
$users = User::all();
$bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) {
// Process each user
sleep(1); // Simulate work
$bar->advance();
}
$bar->finish();
$this->info("\nAll users processed!");
}
protected function showSystemStats()
{
$stats = [
['Users', User::count()],
['Active Users', User::where('last_login', '>', now()->subDay())->count()],
['Orders', \App\Models\Order::count()],
['Pending Orders', \App\Models\Order::where('status', 'pending')->count()],
];
$this->table(['Metric', 'Count'], $stats);
}
<?php
// app/Console/Commands/GenerateReportsCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\ReportService;
use App\Services\ExportService;
class GenerateReportsCommand extends Command
{
protected $signature = 'reports:generate
{type : sales|users|products}
{--period=monthly : daily|weekly|monthly}
{--export= : csv|excel|pdf}';
protected $description = 'Generate various application reports';
protected $reportService;
protected $exportService;
public function __construct(ReportService $reportService, ExportService $exportService)
{
parent::__construct();
$this->reportService = $reportService;
$this->exportService = $exportService;
}
public function handle()
{
$type = $this->argument('type');
$period = $this->option('period');
$exportFormat = $this->option('export');
$this->info("Generating {$type} report for {$period} period...");
try {
$data = $this->reportService->generate($type, $period);
if ($exportFormat) {
$filename = $this->exportService->export($data, $exportFormat, "{$type}-report");
$this->info("Report exported to: {$filename}");
} else {
$this->displayReport($data, $type);
}
} catch (\Exception $e) {
$this->error("Error generating report: " . $e->getMessage());
return 1;
}
return 0;
}
protected function displayReport($data, $type)
{
switch ($type) {
case 'sales':
$this->displaySalesReport($data);
break;
case 'users':
$this->displayUsersReport($data);
break;
case 'products':
$this->displayProductsReport($data);
break;
}
}
protected function displaySalesReport($data)
{
$this->table(
['Date', 'Orders', 'Revenue', 'Average Order'],
$data
);
}
protected function displayUsersReport($data)
{
$this->table(
['Period', 'New Users', 'Active Users', 'Growth Rate'],
$data
);
}
protected function displayProductsReport($data)
{
$this->table(
['Product', 'Units Sold', 'Revenue', 'Stock Level'],
$data
);
}
}
<?php
// app/Console/Commands/ProcessBigDataCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class ProcessBigDataCommand extends Command implements ShouldQueue
{
use Dispatchable;
protected $signature = 'data:process
{file : Path to data file}
{--chunk=1000 : Records per chunk}
{--queue : Process in background}';
protected $description = 'Process large data files in chunks';
public function handle()
{
$file = $this->argument('file');
$chunkSize = $this->option('chunk');
$shouldQueue = $this->option('queue');
if ($shouldQueue) {
$this->info('Dispatching to queue...');
self::dispatch($file, $chunkSize);
$this->info('Job queued successfully!');
return;
}
$this->processFile($file, $chunkSize);
}
protected function processFile($file, $chunkSize)
{
if (!file_exists($file)) {
$this->error("File not found: {$file}");
return;
}
$totalLines = $this->countLines($file);
$this->info("Processing {$totalLines} records in chunks of {$chunkSize}...");
$bar = $this->output->createProgressBar($totalLines);
$handle = fopen($file, 'r');
$header = fgetcsv($handle); // Skip header
$chunk = [];
$processed = 0;
while (($row = fgetcsv($handle)) !== false) {
$chunk[] = array_combine($header, $row);
if (count($chunk) >= $chunkSize) {
$this->processChunk($chunk);
$processed += count($chunk);
$bar->advance(count($chunk));
$chunk = [];
}
}
// Process remaining records
if (!empty($chunk)) {
$this->processChunk($chunk);
$processed += count($chunk);
$bar->advance(count($chunk));
}
fclose($handle);
$bar->finish();
$this->info("\nProcessed {$processed} records successfully!");
}
protected function countLines($file)
{
$lineCount = 0;
$handle = fopen($file, 'r');
while (!feof($handle)) {
fgets($handle);
$lineCount++;
}
fclose($handle);
return $lineCount - 1; // Exclude header
}
protected function processChunk($chunk)
{
// Process your data chunk here
foreach ($chunk as $record) {
// Import/process each record
// This could be saving to database, calling APIs, etc.
}
}
}
<?php
// tests/Unit/Console/UserManagerCommandTest.php
namespace Tests\Unit\Console;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
class UserManagerCommandTest extends TestCase
{
use RefreshDatabase;
public function test_user_creation()
{
$this->artisan('user:manage create', [
'--name' => 'Test User',
'--email' => 'test@example.com',
'--password' => 'secret123'
])
->expectsOutput('User created successfully!')
->assertExitCode(0);
$this->assertDatabaseHas('users', [
'name' => 'Test User',
'email' => 'test@example.com'
]);
}
public function test_user_list()
{
User::factory()->count(3)->create();
$this->artisan('user:manage list')
->expectsOutput('Total users: 3')
->assertExitCode(0);
}
public function test_user_deletion_with_confirmation()
{
$user = User::factory()->create();
$this->artisan('user:manage delete', ['--id' => $user->id])
->expectsConfirmation("Are you sure you want to delete user: {$user->name} ({$user->email})?", 'yes')
->expectsOutput('User deleted successfully!')
->assertExitCode(0);
$this->assertDatabaseMissing('users', ['id' => $user->id]);
}
public function test_password_reset()
{
$user = User::factory()->create();
$this->artisan('user:manage reset-password', [
'--id' => $user->id,
'--password' => 'newpassword123'
])
->expectsOutput('Password reset successfully!')
->assertExitCode(0);
}
}
<?php
// tests/Unit/Console/GenerateReportsCommandTest.php
namespace Tests\Unit\Console;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
class GenerateReportsCommandTest extends TestCase
{
use RefreshDatabase;
public function test_sales_report_generation()
{
$this->artisan('reports:generate', ['type' => 'sales'])
->expectsOutput('Generating sales report for monthly period...')
->assertExitCode(0);
}
public function test_report_export_to_csv()
{
Storage::fake('exports');
$this->artisan('reports:generate', [
'type' => 'sales',
'--export' => 'csv'
])
->expectsOutput('Report exported to:')
->assertExitCode(0);
// Assert file was created
Storage::disk('exports')->assertExists('sales-report-*.csv');
}
public function test_invalid_report_type()
{
$this->artisan('reports:generate', ['type' => 'invalid'])
->expectsOutput('Error generating report:')
->assertExitCode(1);
}
public function test_command_with_progress_bar()
{
$this->artisan('data:process', ['file' => 'test.csv'])
->expectsOutput('Processing')
->assertExitCode(0);
}
}
<?php
// app/Console/Kernel.php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule)
{
// Daily database maintenance at 2:00 AM
$schedule->command('db:maintenance --days=30 --optimize')
->dailyAt('02:00')
->environments(['production']);
// Generate sales reports every Monday at 6:00 AM
$schedule->command('reports:generate sales --period=weekly --export=csv')
->weeklyOn(1, '06:00')
->emailOutputTo('admin@example.com');
// Process data files every hour
$schedule->command('data:process /path/to/data.csv --queue')
->hourly()
->withoutOverlapping();
// Clean temporary files every day
$schedule->command('temp:clean')
->daily();
// Backup database every Sunday at 3:00 AM
$schedule->command('db:maintenance --backup')
->sundays()
->at('03:00')
->environments(['production']);
}
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}
// Good
protected $signature = 'reports:generate-sales
{period=monthly : Report period}
{--export= : Export format}';
// Avoid
protected $signature = 'gen:rep {p} {--e}';
protected $description = 'Generate sales reports for specified period.
Supports daily, weekly, and monthly periods.
Optional export to CSV, Excel, or PDF formats.';
public function handle()
{
try {
// Command logic
$this->info('Command completed successfully!');
return 0; // Success
} catch (\Exception $e) {
$this->error('Command failed: ' . $e->getMessage());
return 1; // General error
}
}
public function handle()
{
$users = User::cursor(); // Uses generator to save memory
$bar = $this->output->createProgressBar(User::count());
foreach ($users as $user) {
$this->processUser($user);
$bar->advance();
}
$bar->finish();
}
// In config/commands.php
return [
'db_maintenance' => [
'cleanup_days' => 30,
'optimize_tables' => true,
],
];
// In command
protected $signature = 'db:maintenance
{--days=' . config('commands.db_maintenance.cleanup_days') . '}';
// Bad: Loads all records into memory
$users = User::all();
// Good: Processes in chunks
User::chunk(1000, function ($users) {
foreach ($users as $user) {
$this->processUser($user);
}
});
// Better: Uses cursor for large datasets
foreach (User::cursor() as $user) {
$this->processUser($user);
}
public function handle()
{
set_time_limit(0); // Remove time limit for long-running commands
// Or use Laravel's timeout handling
$this->call('another:command', [
'--timeout' => 300 // 5 minutes
]);
}
You've now mastered creating custom Laravel Artisan commands! From simple greeting commands to complex data processing tools, you have the knowledge to build powerful CLI interfaces for your Laravel applications.