Mastering Laravel Commands: Advanced Techniques and Use Cases
Summary
Introduction
Laravel Artisan is a powerful command-line interface included with Laravel. It provides a number of helpful commands that can assist you while you build your application. In addition to the commands provided by Artisan, you can also create your own custom commands for your application.
Custom commands are an essential part of Laravel development, allowing you to automate tasks, perform maintenance, and extend the functionality of your application. In this article, we’ll explore advanced techniques for creating and using Laravel commands, along with practical use cases.
Basic Command Structure
Before diving into advanced techniques, let’s quickly review the basic structure of a Laravel command.
To create a new command, use the following Artisan command:
1
php artisan make:command YourCommandName
This will generate a new command class in app/Console/Commands. The basic structure looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Console\Commands;
use Illuminate\Console\Command;
class YourCommandName extends Command
{
protected $signature = 'command:name';
protected $description = 'Command description';
public function handle()
{
// Command logic goes here
}
}
The $signature property defines the command’s name and any arguments or options. The $description property provides a brief explanation of what the command does. The handle() method contains the main logic of your command.
Advanced Command Techniques
Input and Output
Laravel commands offer various ways to interact with the user and display information.
Arguments and Options
You can define arguments and options in the command signature:
1
protected $signature = 'email:send {user} {--queue=}';
In this example, user is a required argument, and queue is an optional option.
You can access these in your handle() method:
1
2
3
4
5
6
7
public function handle()
{
$userId = $this->argument('user');
$queue = $this->option('queue');
// Command logic using $userId and $queue
}
Prompting for Input
You can ask for user input during command execution:
1
2
3
4
5
6
7
8
9
public function handle()
{
$name = $this->ask('What is your name?');
$password = $this->secret('What is the password?');
if ($this->confirm('Do you wish to continue?')) {
// Confirmed action
}
}
Displaying Output
Laravel provides methods for displaying various types of output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function handle()
{
$this->info('Display this on the screen');
$this->error('Something went wrong');
$this->line('Display this without color');
// Display a table
$headers = ['Name', 'Email'];
$data = [
['John Doe', 'john@example.com'],
['Jane Doe', 'jane@example.com'],
];
$this->table($headers, $data);
// Display a progress bar
$bar = $this->output->createProgressBar(100);
for ($i = 0; $i < 100; $i++) {
$bar->advance();
}
$bar->finish();
}
Dependency Injection
Laravel’s service container allows you to inject dependencies into your command’s constructor or handle() method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use App\Services\EmailService;
class SendEmails extends Command
{
protected $emailService;
public function __construct(EmailService $emailService)
{
parent::__construct();
$this->emailService = $emailService;
}
public function handle()
{
$this->emailService->sendBulkEmails();
}
}
Command Scheduling
You can schedule your commands to run automatically by adding them to the app/Console/Kernel.php file:
1
2
3
4
5
6
7
8
9
protected function schedule(Schedule $schedule)
{
$schedule->command('emails:send')->daily();
$schedule->command('backup:clean')->weekly();
$schedule->command('reports:generate')
->everyMinute()
->between('9:00', '17:00')
->weekdays();
}
Calling Commands from Other Commands
You can call other Artisan commands from within your command:
1
2
3
4
5
6
7
8
9
public function handle()
{
$this->call('cache:clear');
$this->callSilent('email:send', [
'user' => 1,
'--queue' => 'default'
]);
}
Custom Artisan Commands in Packages
When developing Laravel packages, you can include custom Artisan commands. Here’s how to do it:
- Create your command class in your package’s src directory.
- In your package’s service provider, register the command:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace YourVendor\YourPackage;
use Illuminate\Support\ServiceProvider;
use YourVendor\YourPackage\Commands\YourCommand;
class YourPackageServiceProvider extends ServiceProvider
{
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
YourCommand::class,
]);
}
}
}
Use Cases and Examples
Database Management
Custom Migration Command
Let’s create a command that generates a migration for creating a polymorphic relationship:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
class CreatePolymorphicMigration extends Command
{
protected $signature = 'make:polymorphic-migration {name} {table}';
protected $description = 'Create a migration for a polymorphic relationship';
public function handle()
{
$name = $this->argument('name');
$table = $this->argument('table');
$migrationContent = <<<EOT
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Create{$name}Table extends Migration
{
public function up()
{
Schema::create('{$table}', function (Blueprint \$table) {
\$table->id();
\$table->morphs('{$name}able');
\$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('{$table}');
}
}
EOT;
$fileName = date('Y_m_d_His') . "_create_{$table}_table.php";
File::put(database_path("migrations/{$fileName}"), $migrationContent);
$this->info("Migration created: {$fileName}");
}
}
Usage: php artisan make:polymorphic-migration Comment comments
Application Maintenance
Cache Management Command
Let’s create a command that manages various caching operations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class ManageCache extends Command
{
protected $signature = 'cache:manage {action : The action to perform (clear, config, routes, views)}';
protected $description = 'Manage various caching operations';
public function handle()
{
$action = $this->argument('action');
switch ($action) {
case 'clear':
Artisan::call('cache:clear');
$this->info('Application cache cleared.');
break;
case 'config':
Artisan::call('config:cache');
$this->info('Configuration cache refreshed.');
break;
case 'routes':
Artisan::call('route:cache');
$this->info('Route cache refreshed.');
break;
case 'views':
Artisan::call('view:clear');
$this->info('Compiled views cleared.');
break;
default:
$this->error('Invalid action specified.');
return;
}
$this->info('Cache management complete.');
}
}
Usage: php artisan cache:manage clear
API Integrations
Syncing Data with External Service
Here’s an example of a command that syncs user data with an external CRM:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace App\Console\Commands;
use App\Models\User;
use App\Services\CrmService;
use Illuminate\Console\Command;
class SyncUsersWithCrm extends Command
{
protected $signature = 'crm:sync-users';
protected $description = 'Sync user data with external CRM';
protected $crmService;
public function __construct(CrmService $crmService)
{
parent::__construct();
$this->crmService = $crmService;
}
public function handle()
{
$users = User::all();
$bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) {
$this->crmService->syncUser($user);
$bar->advance();
}
$bar->finish();
$this->info("\nUser sync completed.");
}
}
Report Generation
Generate and Email Monthly Report
This command generates a monthly report and emails it to specified recipients:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
namespace App\Console\Commands;
use App\Services\ReportService;
use App\Mail\MonthlyReport;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class GenerateMonthlyReport extends Command
{
protected $signature = 'report:monthly {month?} {year?}';
protected $description = 'Generate and email monthly report';
public function handle(ReportService $reportService)
{
$month = $this->argument('month') ?? date('m');
$year = $this->argument('year') ?? date('Y');
$this->info("Generating report for {$year}-{$month}");
$reportData = $reportService->generateMonthlyReport($year, $month);
$pdf = $reportService->generatePdf($reportData);
$recipients = ['manager@example.com', 'ceo@example.com'];
foreach ($recipients as $recipient) {
Mail::to($recipient)->send(new MonthlyReport($pdf));
}
$this->info('Monthly report generated and sent.');
}
}
Queue Management
Monitor and Restart Failed Queue
This command monitors the failed queue and restarts jobs if needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Artisan;
class MonitorFailedQueue extends Command
{
protected $signature = 'queue:monitor-failed {--threshold=50}';
protected $description = 'Monitor failed queue and restart if threshold is reached';
public function handle()
{
$threshold = $this->option('threshold');
$failedCount = DB::table('failed_jobs')->count();
$this->info("Current failed jobs: {$failedCount}");
if ($failedCount >= $threshold) {
$this->warn("Failed job threshold reached. Restarting failed jobs...");
Artisan::call('queue:retry all');
$this->info("All failed jobs have been pushed back onto the queue.");
} else {
$this->info("Failed job count is below the threshold. No action needed.");
}
}
}
Best Practices
Writing Testable Commands
To make your commands testable, consider the following:
- Use dependency injection for services and repositories.
- Avoid using global functions or facades directly in your command logic.
- Use interfaces for dependencies to allow easy mocking in tests.
Here’s an example of a testable command:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
namespace App\Console\Commands;
use App\Interfaces\UserRepositoryInterface;
use App\Interfaces\NotificationServiceInterface;
use Illuminate\Console\Command;
class NotifyInactiveUsers extends Command
{
protected $signature = 'users:notify-inactive {days=30}';
protected $description = 'Notify users who have been inactive for the specified number of days';
protected $userRepository;
protected $notificationService;
public function __construct(UserRepositoryInterface $userRepository, NotificationServiceInterface $notificationService)
{
parent::__construct();
$this->userRepository = $userRepository;
$this->notificationService = $notificationService;
}
public function handle()
{
$days = $this->argument('days');
$inactiveUsers = $this->userRepository->getInactiveUsers($days);
foreach ($inactiveUsers as $user) {
$this->notificationService->sendInactivityNotification($user);
$this->info("Notified user: {$user->email}");
}
$this->info('All inactive users have been notified.');
}
}
Error Handling and Logging
Proper error handling and logging are crucial for maintainable commands:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Illuminate\Support\Facades\Log;
public function handle()
{
try {
// Command logic here
} catch (\Exception $e) {
$this->error("An error occurred: {$e->getMessage()}");
Log::error('Command failed', [
'command' => $this->signature,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return 1; // Return non-zero to indicate failure
}
return 0; // Return zero to indicate success
}
Performance Considerations
For commands that process large amounts of data:
- Use chunking to reduce memory usage:
1 2 3 4 5
User::chunk(100, function ($users) { foreach ($users as $user) { // Process user } });
- Use queues for long-running tasks:
1 2 3 4 5 6 7 8
public function handle() { User::chunk(100, function ($users) { foreach ($users as $user) { ProcessUserJob::dispatch($user); } }); }
- Show progress for long-running commands: ```php $users = User::all(); $bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) { // Process user $bar->advance(); }
$bar->finish(); ```
Conclusion
Custom Artisan commands are a powerful tool in Laravel development. They allow you to automate tasks, perform maintenance, and extend your application’s functionality. By leveraging advanced techniques such as input/output handling, dependency injection, and scheduling, you can create robust and efficient commands.
Remember to follow best practices like writing testable code, proper error handling, and considering performance implications. With these skills, you can significantly improve your Laravel development workflow and create more maintainable applications.
Experiment with creating your own commands for common tasks in your projects. The more you use custom commands, the more opportunities you’ll find to automate and streamline your development process.