About us Guides Projects Contacts
Админка
please wait

Artisan is Laravel's command-line interface that provides numerous helpful commands for development and production operations. Beyond using built-in commands, creating custom Artisan commands enables automation of repetitive tasks, scheduled jobs, and one-off operations. This guide covers creating professional Artisan commands from a senior developer's perspective.

Why Custom Commands Matter

Custom Artisan commands enable:

  1. Task Automation: Repetitive tasks become one-liners
  2. Scheduled Jobs: Cron-like operations within Laravel
  3. Data Operations: Migrations, imports, exports
  4. Maintenance Tasks: Cleanup, cache management
  5. Developer Tools: Project-specific utilities

Creating Commands

Generate Command

php artisan make:command SendWeeklyReport

This creates app/Console/Commands/SendWeeklyReport.php:

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SendWeeklyReport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'report:weekly';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send weekly report to all active users';
/**
* Execute the console command.
*/
public function handle(): int
{
$this->info('Sending weekly reports...');
// Command logic here
$this->info('Reports sent successfully!');
return Command::SUCCESS;
}
}

Command Signature

// Basic command
protected $signature = 'report:weekly';
// With a required argument
protected $signature = 'user:create {email}';
// With an optional argument
protected $signature = 'user:create {email?}';
// With a default value
protected $signature = 'user:create {[email protected]}';
// With an option (flag)
protected $signature = 'report:weekly {--queue}';
// With an option that has a value
protected $signature = 'report:weekly {--format=pdf}';
// With an option shortcut
protected $signature = 'report:weekly {--Q|queue}';
// Array argument (multiple values)
protected $signature = 'email:send {users*}';
// Array option
protected $signature = 'email:send {--id=*}';
// With descriptions
protected $signature = 'user:create
{email : The email of the user}
{--admin : Whether the user is an admin}
{--role=user : The role to assign}';

Command Input and Output

Getting Input

public function handle(): int
{
// Get argument
$email = $this->argument('email');
// Get all arguments
$allArgs = $this->arguments();
// Get option
$format = $this->option('format');
// Get all options
$allOptions = $this->options();
// Check if the option was passed (flag)
if ($this->option('queue')) {
// Handle queued
}
// Array arguments
$userIds = $this->argument('users'); // Returns an array
return Command::SUCCESS;
}

Prompting for Input

public function handle(): int
{
// Simple question
$name = $this->ask('What is your name?');
// With a default
$name = $this->ask('What is your name?', 'Guest');
// Hidden input (passwords)
$password = $this->secret('Enter the database password');
// Confirmation
if ($this->confirm('Do you wish to continue?')) {
// User confirmed
}
// Confirmation with default
if ($this->confirm('Continue?', true)) {
// Default is yes
}
// Choice/select
$role = $this->choice(
'What is your role?',
['admin', 'user', 'guest'],
0 // Default index
);
// Multiple choice
$permissions = $this->choice(
'Select permissions',
['read', 'write', 'delete'],
null,
null,
true // Allow multiple
);
// Anticipate (autocomplete)
$name = $this->anticipate('What is your name?', ['Taylor', 'Jeffrey']);
return Command::SUCCESS;
}

Writing Output

public function handle(): int
{
// Standard output
$this->line('Plain text');
$this->info('Informational message');
$this->comment('Comment style');
$this->question('Question style');
$this->error('Error message');
$this->warn('Warning message');
$this->newLine();
$this->newLine(3); // 3 blank lines
// Table output
$this->table(
['Name', 'Email', 'Role'],
[
['John', '[email protected]', 'admin'],
['Jane', '[email protected]', 'user'],
]
);
// Progress bar
$users = User::all();
$bar = $this->output->createProgressBar(count($users));
$bar->start();
foreach ($users as $user) {
$this->processUser($user);
$bar->advance();
}
$bar->finish();
$this->newLine();
// Or, simpler, with callback
$this->withProgressBar($users, function ($user) {
$this->processUser($user);
});
return Command::SUCCESS;
}

Practical Examples

Data Import Command

<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class ImportUsers extends Command
{
protected $signature = 'users:import
{file : Path to CSV file}
{--dry-run : Show what would be imported without saving}';
protected $description = 'Import users from CSV file';
public function handle(): int
{
$file = $this->argument('file');
$dryRun = $this->option('dry-run');
if (!file_exists($file)) {
$this->error("File not found: {$file}");
return Command::FAILURE;
}
$csv = array_map('str_getcsv', file($file));
$header = array_shift($csv);
$this->info(sprintf('Found %d users to import', count($csv)));
if ($dryRun) {
$this->warn('DRY RUN - No changes will be made');
}
$imported = 0;
$skipped = 0;
DB::beginTransaction();
try {
$this->withProgressBar($csv, function ($row) use ($header, $dryRun, &$imported, &$skipped) {
$data = array_combine($header, $row);
if (User::where('email', $data['email'])->exists()) {
$skipped++;
return;
}
if (!$dryRun) {
User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password'] ?? 'changeme'),
]);
}
$imported++;
});
if (!$dryRun) {
DB::commit();
} else {
DB::rollBack();
}
$this->newLine();
$this->info("Imported: {$imported}, Skipped: {$skipped}");
return Command::SUCCESS;
} catch (\Exception $e) {
DB::rollBack();
$this->error("Import failed: {$e->getMessage()}");
return Command::FAILURE;
}
}
}

Cleanup Command

<?php
namespace App\Console\Commands;
use App\Models\Session;
use App\Models\PasswordReset;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class CleanupOldData extends Command
{
protected $signature = 'cleanup:old-data
{--days=30 : Delete data older than this many days}
{--force : Skip confirmation}';
protected $description = 'Clean up old sessions, password resets, and temp files';
public function handle(): int
{
$days = (int) $this->option('days');
$cutoff = now()->subDays($days);
$this->info("Cleaning up data older than {$days} days ({$cutoff})");
if (!$this->option('force') && !$this->confirm('Continue?')) {
$this->info('Cancelled.');
return Command::SUCCESS;
}
// Clean sessions
$sessions = Session::where('last_activity', '<', $cutoff->timestamp)->count();
Session::where('last_activity', '<', $cutoff->timestamp)->delete();
$this->info("Deleted {$sessions} old sessions");
// Clean password resets
$resets = PasswordReset::where('created_at', '<', $cutoff)->count();
PasswordReset::where('created_at', '<', $cutoff)->delete();
$this->info("Deleted {$resets} old password resets");
// Clean temp files
$files = Storage::files('temp');
$deleted = 0;
foreach ($files as $file) {
$lastModified = Storage::lastModified($file);
if ($lastModified < $cutoff->timestamp) {
Storage::delete($file);
$deleted++;
}
}
$this->info("Deleted {$deleted} temp files");
return Command::SUCCESS;
}
}

Scheduling Commands

Register in Kernel

app/Console/Kernel.php:

<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected $commands = [
Commands\SendWeeklyReport::class,
Commands\CleanupOldData::class,
];
protected function schedule(Schedule $schedule): void
{
// Run daily at midnight
$schedule->command('cleanup:old-data --force')
->daily()
->at('00:00');
// Run weekly on Monday
$schedule->command('report:weekly')
->weeklyOn(1, '8:00');
// Run hourly
$schedule->command('stats:update')
->hourly();
// Run every 5 minutes
$schedule->command('queue:work --stop-when-empty')
->everyFiveMinutes()
->withoutOverlapping();
// Run with conditions
$schedule->command('backup:database')
->daily()
->environments(['production'])
->onSuccess(function () {
// Notify on success
})
->onFailure(function () {
// Alert on failure
});
// Output to log
$schedule->command('reports:generate')
->daily()
->appendOutputTo(storage_path('logs/reports.log'));
}
}

Cron Setup

Add to the server crontab:

# Run Laravel scheduler every minute
* * * * * cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1

Docker Cron Setup

docker/laravel-schedule:

* * * * * cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1
# Keep an empty line at the end

Dockerfile:

COPY docker/laravel-schedule /etc/cron.d/laravel-schedule
RUN chmod 0644 /etc/cron.d/laravel-schedule && \
crontab /etc/cron.d/laravel-schedule
# Start cron in entrypoint
CMD service cron start && php-fpm

Calling Commands Programmatically

use Illuminate\Support\Facades\Artisan;
// Call command
Artisan::call('users:import', [
'file' => '/path/to/file.csv',
'--dry-run' => true,
]);
// Get output
$output = Artisan::output();
// Queue command
Artisan::queue('report:weekly');
// From another command
public function handle(): int
{
$this->call('cache:clear');
// With arguments
$this->call('user:create', [
'email' => '[email protected]',
'--admin' => true,
]);
// Silently (no output)
$this->callSilently('migrate');
return Command::SUCCESS;
}

Key Takeaways

  1. Use descriptive signatures: Clear names and argument descriptions
  2. Handle errors gracefully: Return appropriate exit codes
  3. Support dry-run mode: Let users preview destructive operations
  4. Use progress bars: Visual feedback for long operations
  5. Schedule with constraints: Use withoutOverlapping() for safety
  6. Log command output: Track scheduled command results

Artisan commands are powerful tools for automation—well-designed commands save hours of manual work and reduce human error.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive