This document is a tutorial for developing Laravel applications with custom artisan commands using revolution/laravel-console-starter. This starter kit is designed to accelerate the development of Laravel applications that primarily use artisan commands for their functionality, leveraging the full Laravel framework ecosystem rather than building standalone CLI tools.
Benefits of using revolution/laravel-console-starter:
- Quick Setup: Start developing console applications immediately without complex configurations.
- Laravel Ecosystem: Utilize Laravel's rich features (task scheduling, database, notifications, etc.) in your console application.
- Artisan Commands: Leverage Laravel's powerful Artisan command system to easily create and manage your own commands.
- Testability: Write tests for your console commands using Laravel's testing framework.
Please ensure the following software is installed before proceeding with this tutorial.
- PHP: Version
^8.2(or later) - Composer: PHP dependency management tool (https://getcomposer.org/)
- Laravel Installer: Tool for easily creating Laravel projects (
composer global require laravel/installer)
To create a new Laravel console application project, execute the following command in your terminal:
laravel new my-app --using=revolution/laravel-console-starter --no-interactionThis will create a new directory named my-app and set up the basic structure for your console application.
Main directory structure after installation:
app/Console/Commands/: Your custom Artisan commands will be placed here.config/: Stores application configuration files..env: File for environment-specific settings.routes/console.php: File for registering Artisan commands.
During the project installation process, several initial settings are automatically configured.
.envFile: Based on the.env.examplefile, an.envfile is automatically copied and created. This file contains environment-specific settings such as database connection information and mail settings.- Application Key: The
php artisan key:generatecommand is automatically executed, and the necessary key for application encryption is set toAPP_KEYin the.envfile.
By default, several settings are configured as follows:
- Mail Settings: Mail sending is configured to be logged (
MAIL_MAILER=log). If you need to send actual emails, please configure services like Mailgun, Postmark, or SES.
To create a new Artisan command, use the make:command Artisan command.
php artisan make:command YourCommandName --command=your:commandYourCommandName: The class name of the command to be created (e.g.,SendDailyReport).your:command: The actual command name used to call the command (e.g.,report:send-daily).
Executing this command will generate a file named app/Console/Commands/YourCommandName.php.
Below is an example of a simple command. This command outputs a message to the log when executed.
Edit app/Console/Commands/HelloWorldCommand.php as follows:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class HelloWorldCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'hello:world'; // The command signature (how it's called)
/**
* The console command description.
*
* @var string
*/
protected $description = 'Displays a hello world message in the log'; // Description of the command
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Log::info('Hello, World from Artisan Command!'); // Output message to the log
$this->info('Hello, World message has been logged.'); // Also output message to the console
return Command::SUCCESS;
}
}In the example above:
- The command was created with
make:command HelloWorldCommand --command=hello:world. - The
$signatureproperty defines the command's call name ashello:world. - The
$descriptionproperty defines a short description of the command. This is displayed whenphp artisan listis executed. - The command's logic is written within the
handlemethod. Here,Log::info()is used to write a message tostorage/logs/laravel.log, and$this->info()displays a message in the console.
To run the created command, use the following Artisan command in your terminal:
php artisan your:commandFor example, to run the HelloWorldCommand above, execute the following command:
php artisan hello:worldAfter execution, you can confirm that the message "Hello, World from Artisan Command!" is outputted to the storage/logs/laravel.log file and the console.
If you want to run your commands periodically, you can easily set up cron-like scheduled execution using GitHub Actions. This approach eliminates the need for traditional server-based cron jobs and allows you to run your scheduled tasks in the cloud.
Laravel Console Starter includes a pre-configured example workflow file (.github/workflows/cron.yml) that demonstrates how to set up scheduled task execution. Here's a detailed explanation of this file:
name: cron # Name of the workflow
on:
schedule:
- cron: '0 0 * * *' #UTC # Schedule expression (runs at midnight UTC daily)
jobs:
cron:
name: cron # Name of the job
runs-on: ubuntu-latest # Runner environment
steps:
- name: Checkout # Step 1: Check out the repository
uses: actions/checkout@v4
- name: Setup PHP # Step 2: Set up PHP environment
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
coverage: none
- name: Install Dependencies # Step 3: Install Composer dependencies
run: composer install --no-dev -q
- name: Copy Environment File # Step 4: Set up environment
run: cp .env.example .env
- name: Generate Application Key # Step 5: Generate app key
run: php artisan key:generate
- name: Run Command # Step 6: Run your Artisan command
run: php artisan inspireTo customize this workflow for your own commands:
-
Adjust the Schedule: Modify the
cronexpression in theschedulesection to set your desired frequency. The format follows the standard cron syntax:minute hour day-of-month month day-of-week. For example:'0 0 * * *': Daily at midnight UTC'0 */6 * * *': Every 6 hours'0 0 * * 1': Weekly on Monday at midnight
-
Change the Command: Replace
php artisan inspirein the "Run Command" step with your own command:- name: Run Command run: php artisan your:command
-
Run Multiple Commands: To run multiple commands, you can add additional steps or use shell operators:
- name: Run Commands run: | php artisan first:command php artisan second:command
When your commands require access to sensitive information like API keys, database credentials, or service tokens, you should use GitHub repository secrets:
-
Store Secrets in GitHub:
- Go to your repository on GitHub
- Navigate to Settings > Secrets and variables > Actions
- Click "New repository secret"
- Add your secrets (e.g.,
API_KEY,DATABASE_PASSWORD)
-
Access Secrets in Workflow:
- name: Run Command with Secrets run: php artisan your:command env: API_KEY: ${{ secrets.API_KEY }} DB_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
By using GitHub Actions for task scheduling, you can run your Laravel console commands on a regular schedule without maintaining a server with cron jobs. This approach is especially useful for tasks like data processing, reporting, monitoring, and sending notifications.
If you want to be notified of command execution results or errors, you can use Laravel's notification system. Laravel's built-in notification system provides a convenient way to send notifications from your console commands. This is particularly useful for:
- Alerting you when a long-running task completes
- Reporting errors or issues encountered during command execution
- Sending updates or summaries to email, Slack, or other chat platforms
To create a notification class, use the make:notification Artisan command:
php artisan make:notification TaskCompletedThis will generate a new notification class in the app/Notifications directory. You can customize this class to include the information you want to send in your notification.
Laravel supports various notification channels out of the box, including:
- Slack
- SMS (via services like Vonage)
- Database
To configure these channels, you may need to publish the relevant configuration files if they don't already exist in your config directory:
php artisan config:publish mail
php artisan config:publish servicesThe config/mail.php file allows you to configure your mailer settings, while config/services.php is used to store credentials and settings for various third-party services that Laravel can integrate with for notifications.
Here's an example of how to send a notification from a console command:
<?php
namespace App\Console\Commands;
use App\Notifications\TaskCompleted;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
class ProcessDataCommand extends Command
{
protected $signature = 'data:process';
protected $description = 'Process data and send notification when complete';
public function handle()
{
// Your command logic here
$this->info('Processing data...');
// Process data...
// Send notification when complete
Notification::route('mail', 'admin@example.com')
->notify(new TaskCompleted('Data processing completed successfully'));
return Command::SUCCESS;
}
}For detailed setup and usage, please refer to the official Laravel Notification documentation.
The following examples demonstrate how to build practical applications using Laravel Console Starter. Each example includes detailed implementation steps and shows how to configure necessary services and notifications.
This example demonstrates how to create a command that checks if your websites are online and sends alerts to Slack when issues are detected.
Step 1: Install Slack Notification Channel
composer require laravel/slack-notification-channelStep 2: Create the Command
php artisan make:command MonitorWebsites --command=monitor:websitesStep 3: Create a Notification
php artisan make:notification WebsiteDownStep 4: Configure Slack
First, publish the services configuration:
php artisan config:publish servicesThen, add your Slack webhook URL to the .env file:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
And update the config/services.php file:
'slack' => [
'webhook_url' => env('SLACK_WEBHOOK_URL'),
],Step 5: Implement the Notification
Edit app/Notifications/WebsiteDown.php:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class WebsiteDown extends Notification
{
protected $website;
protected $error;
public function __construct($website, $error)
{
$this->website = $website;
$this->error = $error;
}
public function via($notifiable)
{
return ['slack'];
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->error()
->content('Website Down Alert!')
->attachment(function ($attachment) {
$attachment->title($this->website)
->content("Error: {$this->error}")
->timestamp(now());
});
}
}Step 5: Implement the Command
Edit app/Console/Commands/MonitorWebsites.php:
<?php
namespace App\Console\Commands;
use App\Notifications\WebsiteDown;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class MonitorWebsites extends Command
{
protected $signature = 'monitor:websites {--timeout=10}';
protected $description = 'Check if websites are online and send alerts if they are down';
protected $websites = [
'https://example.com',
'https://yourwebsite.com',
// Add more websites as needed
];
public function handle()
{
$timeout = $this->option('timeout');
$this->info("Checking " . count($this->websites) . " websites (timeout: {$timeout}s)...");
foreach ($this->websites as $website) {
$this->info("Checking {$website}...");
try {
$response = Http::timeout($timeout)->get($website);
if ($response->successful()) {
$this->info("{$website} is up and running!");
Log::info("Website {$website} is up", ['status' => $response->status()]);
} else {
$this->error("{$website} returned status code: " . $response->status());
$this->sendAlert($website, "HTTP status code: " . $response->status());
}
} catch (\Exception $e) {
$this->error("{$website} is down: " . $e->getMessage());
$this->sendAlert($website, $e->getMessage());
}
}
return Command::SUCCESS;
}
protected function sendAlert($website, $error)
{
$this->info("Sending alert for {$website}...");
Log::error("Website {$website} is down", ['error' => $error]);
// Send notification to Slack
Notification::route('slack', config('services.slack.webhook_url'))
->notify(new WebsiteDown($website, $error));
}
}Step 7: Schedule in GitHub Actions
Update your .github/workflows/cron.yml file to run the command every hour:
name: Website Monitoring
on:
schedule:
- cron: '0 * * * *' # Run hourly
jobs:
monitor:
runs-on: ubuntu-latest
steps:
# ... standard setup steps ...
- name: Monitor Websites
run: php artisan monitor:websites --timeout=30
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}This example shows how to create a command that fetches cryptocurrency prices and sends a daily portfolio summary to Discord.
Step 1: Install the Discord Notification Package
composer require revolution/laravel-notification-discord-webhookStep 2: Create the Command
php artisan make:command CryptoPortfolio --command=crypto:portfolioStep 3: Create a Notification
php artisan make:notification CryptoPortfolioUpdateStep 4: Configure Discord
First, publish the services configuration:
php artisan config:publish servicesThen, add your Discord webhook URL to the .env file:
DISCORD_WEBHOOK=https://discord.com/api/webhooks/...
And update the config/services.php file:
'discord' => [
'webhook' => env('DISCORD_WEBHOOK'),
],Step 5: Implement the Notification
Edit app/Notifications/CryptoPortfolioUpdate.php:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use Revolution\Laravel\Notification\DiscordWebhook\DiscordChannel;
use Revolution\Laravel\Notification\DiscordWebhook\DiscordMessage;
class CryptoPortfolioUpdate extends Notification
{
protected $portfolio;
protected $totalValue;
public function __construct($portfolio, $totalValue)
{
$this->portfolio = $portfolio;
$this->totalValue = $totalValue;
}
public function via($notifiable)
{
return [DiscordChannel::class];
}
public function toDiscordWebhook($notifiable)
{
$message = "💰 **Daily Crypto Portfolio Update** 💰\n\n";
$message .= "Total Portfolio Value: $" . number_format($this->totalValue, 2) . "\n\n";
foreach ($this->portfolio as $coin) {
$change = $coin['change_24h'] > 0 ? "↗️ +" : "↘️ ";
$message .= "**{$coin['symbol']}**: $" . number_format($coin['price'], 2) . " ({$change}{$coin['change_24h']}%)\n";
$message .= "Holdings: {$coin['amount']} ({$coin['value']})\n\n";
}
return DiscordMessage::create(content: $message);
}
}Step 6: Implement the Command
Edit app/Console/Commands/CryptoPortfolio.php:
<?php
namespace App\Console\Commands;
use App\Notifications\CryptoPortfolioUpdate;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class CryptoPortfolio extends Command
{
protected $signature = 'crypto:portfolio';
protected $description = 'Fetch cryptocurrency prices and send portfolio update';
// Define your portfolio here
protected $holdings = [
'BTC' => 0.5,
'ETH' => 5,
'SOL' => 20,
// Add more coins as needed
];
public function handle()
{
$this->info('Fetching cryptocurrency prices...');
try {
// Using CoinGecko API (free tier)
$response = Http::get('https://api.coingecko.com/api/v3/coins/markets', [
'vs_currency' => 'usd',
'ids' => 'bitcoin,ethereum,solana', // Match with your holdings
'order' => 'market_cap_desc',
'per_page' => 100,
'page' => 1,
]);
if (!$response->successful()) {
throw new \Exception('API request failed: ' . $response->status());
}
$data = $response->json();
$portfolio = [];
$totalValue = 0;
foreach ($data as $coin) {
$symbol = strtoupper($coin['symbol']);
if (isset($this->holdings[$symbol])) {
$amount = $this->holdings[$symbol];
$value = $amount * $coin['current_price'];
$totalValue += $value;
$portfolio[] = [
'name' => $coin['name'],
'symbol' => $symbol,
'price' => $coin['current_price'],
'change_24h' => $coin['price_change_percentage_24h'],
'amount' => $amount,
'value' => '$' . number_format($value, 2),
];
$this->info("{$symbol}: \${$coin['current_price']} ({$coin['price_change_percentage_24h']}%)");
}
}
$this->info('Total portfolio value: $' . number_format($totalValue, 2));
// Send notification
$webhookUrl = config('services.discord.webhook');
Notification::route('discord-webhook', $webhookUrl)
->notify(new CryptoPortfolioUpdate($portfolio, $totalValue));
$this->info('Portfolio update sent to Discord!');
} catch (\Exception $e) {
$this->error('Error: ' . $e->getMessage());
Log::error('Crypto portfolio update failed', ['error' => $e->getMessage()]);
return Command::FAILURE;
}
return Command::SUCCESS;
}
}Step 7: Schedule in GitHub Actions
Update your .github/workflows/cron.yml file to run the command daily:
name: Crypto Portfolio Update
on:
schedule:
- cron: '0 8 * * *' # Run daily at 8 AM UTC
jobs:
crypto-update:
runs-on: ubuntu-latest
steps:
# ... standard setup steps ...
- name: Update Crypto Portfolio
run: php artisan crypto:portfolio
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}This example demonstrates how to create a command that scrapes content from a website and sends the extracted data via email notification.
Step 1: Create the Command
php artisan make:command WebScraper --command=scrape:websiteStep 2: Create a Notification
php artisan make:notification ScrapingCompletedStep 3: Configure Email
First, publish the mail configuration:
php artisan config:publish mailThen, update your .env file with your mail settings:
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=your-app@example.com
MAIL_FROM_NAME="${APP_NAME}"
Step 4: Implement the Notification
Edit app/Notifications/ScrapingCompleted.php:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ScrapingCompleted extends Notification
{
use Queueable;
protected $success;
protected $url;
protected $title;
protected $content;
protected $error;
public function __construct($success, $url = null, $title = null, $content = null, $error = null)
{
$this->success = $success;
$this->url = $url;
$this->title = $title;
$this->content = $content;
$this->error = $error;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
$message = (new MailMessage)
->subject($this->success ? 'Website Scraping Completed Successfully' : 'Website Scraping Failed');
if ($this->success) {
$message->line('The website content has been successfully scraped.')
->line('URL: ' . $this->url)
->line('Title: ' . $this->title)
->line('Content Preview: ' . $this->getContentPreview())
->line('Date: ' . now()->format('Y-m-d H:i:s'))
->success();
} else {
$message->line('The website scraping has failed.')
->line('URL: ' . $this->url)
->line('Error: ' . $this->error)
->line('Date: ' . now()->format('Y-m-d H:i:s'))
->error();
}
return $message;
}
protected function getContentPreview($maxLength = 200)
{
if (empty($this->content)) {
return 'No content available';
}
$content = strip_tags($this->content);
if (strlen($content) <= $maxLength) {
return $content;
}
return substr($content, 0, $maxLength) . '...';
}
}Step 5: Implement the Command
Edit app/Console/Commands/WebScraper.php:
<?php
namespace App\Console\Commands;
use App\Notifications\ScrapingCompleted;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class WebScraper extends Command
{
protected $signature = 'scrape:website {--url=https://example.com} {--email=admin@example.com}';
protected $description = 'Scrape content from a website and send notification';
public function handle()
{
$url = $this->option('url');
$email = $this->option('email');
$this->info("Starting to scrape content from {$url}...");
try {
// Make HTTP request to the website
$response = Http::timeout(30)->get($url);
// Check if request was successful
if (!$response->successful()) {
throw new \Exception("HTTP request failed with status code: " . $response->status());
}
$html = $response->body();
// Extract title
preg_match('/<title>(.*?)<\/title>/i', $html, $titleMatches);
$title = $titleMatches[1] ?? 'No title found';
// Extract main content (simplified approach)
preg_match('/<body.*?>(.*?)<\/body>/is', $html, $bodyMatches);
$body = $bodyMatches[1] ?? '';
// Clean up content - remove scripts, styles, and comments
$content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $body);
$content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
$content = preg_replace('/<!--(.*?)-->/is', '', $content);
// Extract text from the first paragraph as a simple example
preg_match('/<p>(.*?)<\/p>/is', $content, $paragraphMatches);
$mainContent = $paragraphMatches[1] ?? 'No content found';
$this->info("Scraping completed successfully!");
$this->info("Title: {$title}");
$this->info("Content preview: " . substr(strip_tags($mainContent), 0, 100) . "...");
Log::info("Website scraping completed", [
'url' => $url,
'title' => $title,
'content_length' => strlen($mainContent)
]);
// Send success notification
Notification::route('mail', $email)
->notify(new ScrapingCompleted(true, $url, $title, $mainContent));
return Command::SUCCESS;
} catch (\Exception $e) {
$this->error("Scraping failed: " . $e->getMessage());
Log::error("Website scraping failed", [
'url' => $url,
'error' => $e->getMessage()
]);
// Send failure notification
Notification::route('mail', $email)
->notify(new ScrapingCompleted(false, $url, null, null, $e->getMessage()));
return Command::FAILURE;
}
}
}Step 7: Schedule in GitHub Actions
Update your .github/workflows/cron.yml file to run the command daily:
name: Website Scraping
on:
schedule:
- cron: '0 8 * * *' # Run daily at 8 AM UTC
jobs:
scrape:
runs-on: ubuntu-latest
steps:
# ... standard setup steps ...
- name: Scrape Website
run: php artisan scrape:website --email=${{ secrets.ADMIN_EMAIL }}
env:
MAIL_HOST: ${{ secrets.MAIL_HOST }}
MAIL_PORT: ${{ secrets.MAIL_PORT }}
MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }}
MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}These examples demonstrate how to build practical applications using Laravel Console Starter. You can adapt and combine these examples to create your own custom solutions for various use cases.
Congratulations! You've now learned the fundamentals of building Laravel console applications with revolution/laravel-console-starter. You've created your first command, learned how to run it, set up task scheduling with GitHub Actions, implemented notifications, and explored practical examples.
To continue your journey with Laravel console applications:
-
Explore Laravel's Documentation: Dive deeper into Laravel's features that can enhance your console applications at Laravel's official documentation.
-
Implement Testing: Write tests for your commands using Laravel's testing framework to ensure reliability.
-
Explore Package Development: Consider packaging your console commands as reusable Laravel packages if you find yourself creating similar functionality across projects.
-
Contribute to the Community: Share your experiences, tools, and solutions with the Laravel community through blog posts or open-source contributions.
-
Stay Updated: Laravel and its ecosystem evolve rapidly. Follow Laravel News, the official Laravel blog, and relevant GitHub repositories to stay current with best practices.
Remember that console applications can be powerful tools for automation, data processing, and system maintenance. The skills you've learned here can be applied to a wide range of use cases beyond the examples provided.
Happy coding!