Skip to content

Terminal

Precious Lyson edited this page Nov 13, 2026 · 3 revisions

The Machinjiri Terminal is the CLI entry point of the Machinjiri framework. It is built on top of Symfony\Component\Console\Application and provides:

  • Scaffolding generators for controllers, models, middleware, notifications, service providers, views, webhooks, jobs, migrations, seeders, and factories.
  • Runtime utilities for the dev server, database testing, queue management, and network diagnostics.
  • Task scheduling with cron-like tasks.
  • Testing utilities (PHPUnit / Paratest).

If you are familiar with Laravel's artisan, you will feel right at home. The Terminal is modular — every category of commands lives in its own class that returns a list of Symfony Command instances through a static getCommands() method.


Installation

The Terminal ships with the Machinjiri core.

The terminal binary is usually exposed via an artisan or machinjiri file in your project root:

php artisan list

If you need to bootstrap it manually:

<?php
// artisan
require __DIR__ . '/vendor/autoload.php';

use Mlangeni\Machinjiri\Core\Artisans\Terminal\Terminal;

$terminal = new Terminal();
$terminal->run();

Quick Start

# List all available commands
php artisan list

# Get help for a specific command
php artisan help make:controller

# Scaffold a controller
php artisan make:controller UserController

# Scaffold a model
php artisan make:model User

# Start a dev server on port 3000
php artisan run:dev

# Create and run a migration
php artisan db:migration:create create_users_table --blueprint
php artisan db:migration:migrate

# Start a queue worker (daemonized)
php artisan queue:work --daemon --queue=default

Architecture Overview

Terminal
├── extends Symfony\Component\Console\Application
├── registers command classes on construction
└── exposes addCommandClass() for runtime extension

Command Classes (each returns array<Command>):
├── App                    → make:controller, make:model, make:middleware, get:env
├── PHPServerCommands      → run:dev, stop:dev
├── ServiceProviderCommand → provider:make, provider:remove, provider:list, ...
├── QueueWorkerCommand     → queue:make, queue:work, queue:failed, ...
├── ViewCommands           → view:make, view:make-layout, view:make-partial
├── WebhookCommand         → make:webhook
├── Network                → network:ping, network:scan, network:ports, ...
├── DatabaseCommands       → db:migration:*, db:seeder:*, db:factory:*
├── NotificationCommand    → make:notification, notify:send, notify:test, ...
└── TaskSchedulerCommand   → scheduler:list, scheduler:run, scheduler:create-task, ...

Shared Traits:
├── CommandHelper          → executeWithStyle(), artisanContainer()
├── MigrationInstances     → handler(), creator(), seeder(), factory()
├── NetworkTrait           → networkManager()
├── QueueCommandHelper     → queue generator + DB helpers
└── QueueWorkerValidationTrait → name validators

The Terminal class

class Terminal extends Application
{
    public const TERMINAL_VERSION = "2.0";

    protected $commandClasses = [
        App::class,
        PHPServerCommands::class,
        // ... more
    ];

    protected function registerCommands(): void
    {
        foreach ($this->commandClasses as $commandClass) {
            foreach ($commandClass::getCommands() as $command) {
                $this->add($command);
            }
        }
    }
}

Every command class must implement public static function getCommands(): array returning an array of Command objects. This design makes command registration declarative and stateless.


How It Works

The CommandHelper trait

Almost every command uses CommandHelper, which provides two critical methods:

executeWithStyle()

Wraps execution with:

  • A standard title banner
  • Event hooks (command.execute.success / command.execute.failed)
  • Consistent error handling
  • Verbose trace output
protected function execute(InputInterface $input, OutputInterface $output): int
{
    return $this->executeWithStyle($input, $output, 'App Controller', function (SymfonyStyle $ss) use ($input) {
        // your logic
        $ss->success("Done");
        return Command::SUCCESS;
    });
}

artisanContainer()

Walks up the directory tree to locate bootstrap/app.php, loads it, and returns the application's service container:

$container = $this->artisanContainer();
$service = $container->resolve(SomeService::class);

This is what lets commands access services bound in your app, without needing to know how the app bootstraps.


Command Reference

Application Generators

Command Description
make:controller <name> Create a controller in app/Controllers/
make:model <name> Create a model in app/Model/
make:middleware <name> Create a middleware class in app/Middleware/
get:env Display all environment variables

Example:

php artisan make:controller UserController
php artisan make:model User
// app/Controllers/UserController.php (generated)

PHP Development Server

Command Description
run:dev [--port=3000] Start a local PHP dev server (default port 3000)
stop:dev Stop the running dev server

Example:

php artisan run:dev --port=8080
# Server Started :)
# PID => 12345
# address => 127.0.0.1:8080

Logs are written to storage/dev-server/dev_server_YYYY_MM_DD.log.


Database Commands

Migrations

Command Description
db:migration:create <name> [--blueprint] Create a migration file
db:migration:list List all migration files
db:migration:remove <name> Remove a migration file
db:migration:get-ran List migrations that have been applied
db:migration:migrate Run all pending migrations
db:migration:rollback Roll back the latest batch

Example:

php artisan db:migration:create create_users_table --blueprint
php artisan db:migration:migrate
php artisan db:migration:get-ran

💡 Migration names must be lowercase with underscores (create_users_table), and cannot collide with reserved keywords.

Seeders

Command Description
db:seeder:create <name> [--migration] Create a seeder (optionally with a migration)
db:seeder:run <name> Run one seeder
db:seeder:run-all [--with-transaction] Run all seeders (optionally in a transaction)
db:seeder:list List available seeders
db:seeder:refresh Re-run all seeders from scratch

Factories

Command Description
db:factory:create <name> [--migration] Create a factory file
db:factory:run <model> [-c 10] [--fake-only] Generate fake records

Example:

php artisan db:factory:run User --count=50
php artisan db:factory:run User --count=10 --fake-only  # don't persist

Queue Commands

The queue subsystem is arguably the most feature-rich part of the Terminal.

Setup

Command Description
queue:init Initialize the queue driver (creates DB schema for database driver)
queue:make <name> [--type=database] [--config] [--register] [--command] Scaffold a new queue driver class

Workers

Command Description
queue:work Process jobs from the queue (foreground)
queue:job <name> Process jobs of a specific type defined in config/commands.php
queue:supervisor [--concurrency=1] Keep N workers alive
queue:worker-start Start workers (managed)
queue:worker-stop Stop workers
queue:worker-restart Restart workers
queue:worker-status Show worker table (PID, heartbeat, memory)
queue:worker-cleanup Remove orphaned PID files

Worker options (queue:work):

--queue=default       # queue name
--sleep=3             # seconds to sleep when empty
--memory=128          # max MB before restarting
--timeout=60          # job timeout
--tries=3             # attempts before failure
--max-jobs=0          # limit total jobs (0 = unlimited)
--daemon              # run as a daemon
--stop-on-empty       # exit when queue is drained
--force               # override maintenance mode
--pid-file=/tmp/q.pid # write PID to file
--instance=1          # instance number for supervisor mode

Example:

php artisan queue:init
php artisan queue:work --daemon --queue=emails --memory=256

Jobs

Command Description
queue:make-job <name> Create a job class
queue:status Show queue stats
queue:health Check health of the queue driver

Failed Jobs

Command Description
queue:failed List failed jobs
queue:retry <id|all> Retry one or all failed jobs
queue:forget <id> Remove a failed job
queue:flush Clear all failed jobs
queue:clear Empty a queue
queue:db-info Show and test the database driver connection

Example:

php artisan queue:make-job SendWelcomeEmail --type=email --queue=emails
php artisan queue:work --queue=emails --daemon
php artisan queue:failed
php artisan queue:retry all

🔒 queue:work supports graceful shutdown via pcntl signals (SIGINT, SIGTERM) — press Ctrl+C and the current job completes before the worker exits.


Service Provider Commands

Command Description
provider:make <name> Generate a service provider
provider:remove <name> [--config] Remove a service provider
provider:list [--details] List all providers
provider:init Create all basic providers
provider:stub <name> <stub> Generate from a custom stub
provider:register <name> Register a provider in config/providers.php
provider:unregister <name> Unregister a provider
provider:info <name> Show provider info (bindings, singletons, aliases)
provider:thirdparty-auth Scaffold ThirdPartyAuthServiceProvider with OAuth config

Example:

php artisan provider:make PaymentServiceProvider --bind="PaymentGateway=StripeGateway"
php artisan provider:register PaymentServiceProvider
php artisan provider:info PaymentServiceProvider

The generated provider class template supports:

protected array $bindings = [
    'PaymentGateway' => StripeGateway::class,
];

protected array $singletons = [
    'PaymentConfig' => PaymentConfig::class,
];

protected array $aliases = [
    'pay' => 'PaymentGateway',
];

protected bool $defer = false;

View / Template Commands

Command Description
view:make <name> [-l layout] [-f] Create a view template (dot notation)
view:make-layout <name> Create a layout
view:make-partial <name> Create a partial

Example:

php artisan view:make admin.dashboard --layout=layouts.admin
php artisan view:make-layout layouts.admin
php artisan view:make-partial partials.header

admin.dashboard becomes resources/views/admin/dashboard.php.


Webhook Commands

Command Description
make:webhook <name> Generate webhook components (--controller, --config, --handler, --provider, --all)

Generates:

  • Controller → app/Controllers/{Name}WebhookController.php
  • Handler → app/Webhooks/Handlers/{Name}WebhookHandler.php
  • Config entry → config/core/webhooks.php
  • Service Provider → app/Providers/{Name}WebhookServiceProvider.php

Example:

php artisan make:webhook Stripe

Next steps printed after generation:

- Add route: $router->post('/webhook/stripe', [StripeWebhookController::class, 'handle'])
- Set STRIPE_WEBHOOK_SECRET in .env

Notification Commands

Command Description
make:notification <name> [-c mail,sms] [-Q] Create a notification class
notify:channels [--json] List registered channels
notify:list [--path] List discovered notification classes
notify:show <class> Inspect a notification class
notify:send <class> --to=... [--queue] Send a notification ad-hoc
notify:test <channel> --to=... Fire a lightweight test notification
notify:events List emitted notification events

Example:

# Create a notification
php artisan make:notification OrderShipped -c mail,sms,database --queue

# Send it to a one-off recipient
php artisan notify:send OrderShipped \
    --to=user@example.com \
    --data='{"orderId":42}' \
    --queue

# Test a channel
php artisan notify:test mail --to=admin@example.com

Network Commands

Command Description
network:ping <host> [--timeout=2] Ping a host
network:scan <subnet> [--concurrent=20] Scan CIDR subnet for hosts
network:ports <host> [start] [end] [--timeout=2] Scan ports
network:interfaces List local network interfaces
network:monitor [-a host[:alias]] [-w] Monitor hosts (optionally watch)
network:dns <domain> [type] Perform DNS lookup (A, MX, NS, TXT, ...)

Example:

php artisan network:ping google.com
php artisan network:scan 192.168.1.0/24 --concurrent=50
php artisan network:ports example.com 22 443
php artisan network:dns example.com MX

⚠️ These commands require a network.manager service bound in your container:

$container->singleton('network.manager', fn($c) => new NetworkManager(new NetworkConfig()));

Task Scheduler Commands

A cron-like scheduler backed by TaskManager and TaskScheduler.

Command Description
scheduler:list [--enabled] [-g group] List scheduled tasks
scheduler:create-task <name> [-r] [-c cron] [-t type] Create a task class
scheduler:run [-t taskId] [-f] Run due tasks (or a single one)
scheduler:status Show scheduler status
scheduler:enable <id> Enable a task
scheduler:disable <id> Disable a task
scheduler:clean [-d days] Remove execution history older than N days
scheduler:stats [id] Show execution statistics
scheduler:cache-preload Warm up the scheduler cache
scheduler:cache-clear Clear the scheduler cache

Example:

php artisan scheduler:create-task SendDailyReport --cron="0 9 * * *" --register
php artisan scheduler:list --enabled
php artisan scheduler:run
php artisan scheduler:stats 1

Crontab integration:

* * * * * cd /path/to/project && php artisan scheduler:run >> /dev/null 2>&1

Testing

Command Description
test [--parallel] [--coverage] [--group=] [--filter=] [--testsuite=] Run the test suite

Example:

php artisan test --parallel --coverage
php artisan test --filter=testUserCreation --testsuite=Feature

Extending the Terminal

Registering an extra command class at runtime

$terminal = new Terminal();
$terminal->addCommandClass(MyCustomCommands::class);
$terminal->run();

MyCustomCommands just needs:

class MyCustomCommands
{
    public static function getCommands(): array
    {
        return [
            new class extends Command {
                use CommandHelper;
                // ...
            },
        ];
    }
}

Creating a full-fledged command class

<?php

namespace App\Console\Commands;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Style\SymfonyStyle;
use Mlangeni\Machinjiri\Core\Artisans\Terminal\Commands\CommandHelper;

class MyCommands
{
    public static function getCommands(): array
    {
        return [
            new class extends Command {
                use CommandHelper;

                public function __construct()
                {
                    parent::__construct('my:hello');
                    $this->setDescription('Says hello');
                }

                protected function configure(): void
                {
                    $this->addArgument('name', InputArgument::REQUIRED, 'Your name');
                }

                protected function execute(InputInterface $input, OutputInterface $output): int
                {
                    return $this->executeWithStyle($input, $output, 'Hello Command', function (SymfonyStyle $ss) use ($input) {
                        $name = $input->getArgument('name');
                        $ss->success("Hello, {$name}!");
                        return Command::SUCCESS;
                    });
                }
            },
        ];
    }
}

Register it:

$terminal->addCommandClass(\App\Console\Commands\MyCommands::class);

Best Practices

1. Always wrap execution with executeWithStyle()

It guarantees:

  • Consistent styling
  • Structured error handling
  • Event hooks fire automatically
return $this->executeWithStyle($input, $output, 'My Task', function (SymfonyStyle $ss) use ($input) {
    // Your business logic
    return Command::SUCCESS;
});

2. Never assume the working directory

Use $this->artisanContainer() to resolve paths:

$app = $this->artisanContainer();
$controllersDir = $app->app . '/Controllers/';

3. Use Keywords::internal() for naming validation

Reserved names like class, interface, trait should never collide with user-supplied migration/seeder names.

4. Prefer symfony/console Table, SymfonyStyle

For rich CLI output use:

$ss->table(['Column A', 'Column B'], $rows);
$ss->section('Section Title');
$ss->listing(['item 1', 'item 2']);
$ss->progressStart(100);

5. Handle verbosity

if ($output->isVerbose()) {
    $ss->text('Debug: ' . print_r($data, true));
}

6. Signal handling for long-running commands

Wrap heavy loops with signal checks, especially queue workers. The ArtisanBackgroundWorker already does this.


Troubleshooting

"Could not find artisan bootstrap file"

CommandHelper::artisanContainer() walks up to 10 levels looking for bootstrap/app.php. Make sure:

  • You run commands from within the project directory.
  • bootstrap/app.php exists and returns a Container.

"Class already exists or unreadable directory"

Check write permissions in app/Controllers, database/migrations, etc.

Queue worker fails with "database tables not found"

php artisan queue:init

This will create queue_jobs and queue_failed_jobs tables via DatabaseQueueSchema.

pcntl extension not found

Queue workers will fall back to polling without signal handling. Install pcntl for graceful shutdown.

"Network Manager not bound in AppServiceProvider"

You need to bind network.manager and network.config in a service provider:

$this->app->singleton('network.manager', function ($app) {
    return new \Mlangeni\Machinjiri\Core\Components\Network\Tools\Manager(
        new \Mlangeni\Machinjiri\Core\Components\Network\Tools\NetworkConfig()
    );
});

Advanced Topics

Event system integration

Every command executed through executeWithStyle() fires:

EventListener::artisan('command.execute.success', ['module' => $title, 'handler' => $handler]);
EventListener::artisan('command.execute.failed', ['module' => $title, 'error' => $e->getMessage()]);

You can subscribe to these to add metrics, logging, or notifications:

EventListener::listen('command.execute.failed', function ($ctx) {
    Log::error("Command failed: {$ctx['module']}", $ctx);
});

Building custom schema migrations

DatabaseQueueSchema provides static helpers:

DatabaseQueueSchema::getMySqlEssentialSchema('queue_jobs', 'queue_failed_jobs');
DatabaseQueueSchema::getPgsqlFullSchema();
DatabaseQueueSchema::getSqliteFullSchema();

You can extend this pattern for other subsystems (e.g., CacheSchema, SessionSchema).

Background worker lifecycle

ArtisanBackgroundWorker orchestrates:

  1. Maintenance mode check (skipped with --force).
  2. Options assembly (sleep, memory, timeout, etc.).
  3. PID file creation (with register_shutdown_function() cleanup).
  4. Signal registration (SIGINT/SIGTERM → worker->stop()).
  5. Heartbeat callbacks (if the worker implements setHeartbeatCallback()).
  6. Statistics printed after each run: processed, failed, memory, last job.

Multi-instance supervision

php artisan queue:supervisor --concurrency=4 --daemon

BackgroundWorkerManager keeps exactly N workers alive, restarting crashed ones and cleaning orphaned PIDs on demand via queue:worker-cleanup.

Custom notification channels

Register in your NotificationManager:

$manager->extend('slack', function ($config) {
    return new SlackChannel($config['webhook_url']);
});

Then in a notification:

public function via(NotifiableInterface $notifiable): array
{
    return ['mail', 'slack'];
}

public function toSlack(NotifiableInterface $n): array
{
    return ['text' => 'Order shipped!'];
}

Summary

The Machinjiri Terminal is a modular, extensible command framework that covers:

Area Purpose
Generators Scaffold classes for controllers, models, migrations, jobs, notifications, providers, views, webhooks
Runtime Dev server, database connectivity tests, network diagnostics
Data Migrations, seeders, factories
Async Queue workers, jobs, failed job retry, supervisor management
Messaging Notification channels, ad-hoc sends, inspection
Ops Task scheduler with cron expressions, cache preload/clear, execution stats

Its design (getCommands() + CommandHelper + artisanContainer()) makes it trivial to add your own commands without touching the framework core.

Clone this wiki locally