PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
240: Building CLI Tools with Laravel Artisan Commands
I've noticed a recurring habit among developers moving into larger Laravel projects: when they need to automate a task—like cleaning up old logs or syncing data from an external API—they create a folder called /scripts in the root directory and throw a standalone sync_data.php file in there. They think they're being "efficient" by avoiding the overhead of the framework for a "simple" script.
"I can just run a standalone PHP script for background tasks"
Here is why that approach is a trap. If you write a standalone script, you lose everything that makes Laravel powerful. You don't have access to your Eloquent models, your Service Container, your configured Mailers, or your environment variables without manually bootstrapping the entire framework kernel inside that script. It's a messy, brittle process that usually leads to a Fatal error: Class 'App\Models\User' not found because the autoloader wasn't handled correctly.
If you're already inside a Laravel project, your CLI tool should be an Artisan command. By doing this, your script lives inside the application ecosystem. You can inject dependencies directly into the handle() method, and you get a standardized way to handle input and output.
Leveraging the Service Container via Artisan Commands
Let's look at a real scenario. Imagine you need to build a tool that fetches current product prices from a supplier's API and updates your local database. Instead of a raw script, we use php artisan make:command SyncProductPrices.
In the resulting class, the handle() method is where the magic happens. Because it's an Artisan command, Laravel's container handles the dependencies for you. I personally love this because it makes testing a breeze.
public function handle(ProductPriceService $priceService)
{
$this->info('Fetching latest prices from supplier...');
try {
$count = $priceService->updateAllPrices();
$this->info("Successfully updated {$count} products.");
} catch (\Exception $e) {
$this->error('Sync failed: ' . $e->getMessage());
return 1; // Return a non-zero exit code on failure
}
}
Notice how I injected ProductPriceService? I didn't have to instantiate it or worry about its own dependencies. I also used $this->info() and $this->error(). These aren't just echo statements; they provide formatted, color-coded output that makes your CLI tool feel professional.
Defining Signatures and Handling User Input
One of the most powerful parts of Artisan commands is the $signature property. This is where you define how the command is called and what arguments or options it accepts. You don't have to parse $argv manually like you would in a plain PHP script.
Let's say we want to allow the user to sync only a specific category of products, or perhaps run a "dry run" to see what would happen without actually changing the database. Your signature would look like this:
protected $signature = 'app:sync-prices {category?} {--dry-run}';
The {category?} is an optional argument, and {--dry-run} is a boolean flag (an option). Inside the handle() method, you can retrieve these easily:
$category = $this->argument('category');
$isDryRun = $this->option('dry-run');
if ($isDryRun) {
$this->comment('Dry run enabled. No changes will be saved.');
}
Moving from Manual Execution to the Scheduler
The final piece of the puzzle is that once your logic is wrapped in an Artisan command, you've unlocked the Laravel Scheduler. You no longer have to manually add a cron job for every single script you write.
Instead of adding * * * * * php /path/to/scripts/sync_data.php to your server's crontab, you just add one single entry for the Laravel Scheduler. Then, in your routes/console.php (or app/Console/Kernel.php in older versions), you can define the frequency in plain English:
use Illuminate\Support\Facades\Schedule;
Schedule::command('app:sync-prices')->dailyAt('03:00');
This keeps your automation logic in version control where it belongs, rather than hidden away in a server's crontab file. It's cleaner, safer, and significantly easier for the next developer on your team to discover.
📋 Practical Task
Build a "Stale User Session Cleanup" Tool
Your goal is to create a custom Artisan command that identifies and removes "stale" records from a hypothetical user_sessions table. This is a common task for maintaining database performance in high-traffic apps.
Requirements:
- Generate a command named
app:cleanup-sessions. - The command must accept a required argument
{days}(an integer) to determine how old a session must be to be considered "stale". - The command should include an optional flag
{--force}. If this flag is NOT present, the command should simply output how many records would be deleted (a dry run). If the flag IS present, it should actually perform the deletion. - Use
$this->info()to report the number of records affected and$this->warn()if the command is running in dry-run mode. - Ensure the command returns a proper exit code (0 for success, 1 for failure).
There are no comments for now.