Skip to content

Module Management Guide

This comprehensive guide covers the complete lifecycle management of WhatsmarkSaaS modules, from initial installation through maintenance, updates, and troubleshooting. Master the tools and techniques needed to effectively manage modules in production environments.


Module Lifecycle Overview

Module States

plaintext
Module Lifecycle States
┌─────────────┐    install    ┌─────────────┐    enable    ┌─────────────┐
│   Absent    │ ──────────────▶│  Installed  │ ─────────────▶│   Active    │
└─────────────┘               └─────────────┘              └─────────────┘
                                     │                             │
                                     │                             │
                              uninstall                         disable
                                     │                             │
                                     ▼                             ▼
                              ┌─────────────┐              ┌─────────────┐
                              │  Removed    │              │  Disabled   │
                              └─────────────┘              └─────────────┘

                                                               enable


                                                           ┌─────────────┐
                                                           │   Active    │
                                                           └─────────────┘

State Descriptions:

  • Absent: Module doesn't exist in the system
  • Installed: Module files present but not active
  • Active: Module is enabled and functional
  • Disabled: Module is temporarily deactivated
  • Removed: Module files deleted from system

Installation & Setup

Installing New Modules

Method 1: Manual Installation

bash
# 1. Download and extract module to Modules directory
cd /path/to/whatsmark-saas
mkdir -p Modules/NewModule

# 2. Copy module files
cp -r /path/to/NewModule/* Modules/NewModule/

# 3. Install module dependencies
cd Modules/NewModule
composer install --no-dev --optimize-autoloader

# 4. Register the module
php artisan module:discover

# 5. Enable the module
php artisan module:enable NewModule

# 6. Run migrations if needed
php artisan module:migrate NewModule

# 7. Clear caches
php artisan cache:clear
php artisan config:clear
php artisan view:clear

Method 2: Composer Installation

bash
# Install via Composer (if packaged)
composer require your-company/newmodule-whatsmarksaas

# Enable the module
php artisan module:enable NewModule

# Run setup
php artisan module:setup NewModule

Method 3: Automated Installation Script

Create scripts/install-module.sh:

bash
#!/bin/bash

MODULE_NAME=$1
MODULE_PATH=$2

if [ -z "$MODULE_NAME" ] || [ -z "$MODULE_PATH" ]; then
    echo "Usage: $0 <module-name> <module-path>"
    exit 1
fi

echo "Installing module: $MODULE_NAME"

# Check if module already exists
if [ -d "Modules/$MODULE_NAME" ]; then
    echo "Module $MODULE_NAME already exists"
    exit 1
fi

# Copy module files
echo "Copying module files..."
cp -r "$MODULE_PATH" "Modules/$MODULE_NAME"

# Install dependencies
echo "Installing dependencies..."
cd "Modules/$MODULE_NAME"
if [ -f "composer.json" ]; then
    composer install --no-dev --optimize-autoloader
fi

cd ../../

# Register and enable module
echo "Registering module..."
php artisan module:discover
php artisan module:enable "$MODULE_NAME"

# Run migrations
echo "Running migrations..."
php artisan module:migrate "$MODULE_NAME"

# Clear caches
echo "Clearing caches..."
php artisan cache:clear
php artisan config:clear
php artisan view:clear

echo "Module $MODULE_NAME installed successfully!"

Module Discovery & Registration

WhatsmarkSaaS automatically discovers modules in the Modules/ directory:

php
// Check module discovery status
php artisan module:list

// Force re-discovery
php artisan module:discover

// Show detailed module information
php artisan module:show ModuleName

Activation & Deactivation

Enabling Modules

Single Module Activation

bash
# Enable a specific module
php artisan module:enable ModuleName

# Enable with verbose output
php artisan module:enable ModuleName --verbose

# Enable and run migrations
php artisan module:enable ModuleName --migrate

Bulk Module Activation

bash
# Enable multiple modules
php artisan module:enable Module1 Module2 Module3

# Enable all installed modules
php artisan module:enable-all

# Enable modules matching pattern
php artisan module:enable --pattern="Payment*"

Activation with Dependencies

Create module-dependencies.json:

json
{
    "dependencies": {
        "PaymentGateway": ["BasePayment", "Notifications"],
        "AdvancedReporting": ["BasicReporting", "DataExport"],
        "CustomModule": ["CoreModule"]
    }
}

Custom activation command with dependency resolution:

php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Nwidart\Modules\Facades\Module;

class ModuleEnableWithDependencies extends Command
{
    protected $signature = 'module:enable-deps {module} {--check-only}';
    protected $description = 'Enable module with automatic dependency resolution';

    public function handle()
    {
        $moduleName = $this->argument('module');
        $checkOnly = $this->option('check-only');

        $dependencies = $this->loadDependencies();
        $toEnable = $this->resolveDependencies($moduleName, $dependencies);

        if ($checkOnly) {
            $this->info("Dependencies for {$moduleName}:");
            foreach ($toEnable as $dep) {
                $this->line("  - {$dep}");
            }
            return;
        }

        foreach ($toEnable as $module) {
            if (!Module::find($module)?->isEnabled()) {
                $this->info("Enabling {$module}...");
                $this->call('module:enable', ['module' => $module]);
            }
        }

        $this->info("All dependencies enabled successfully!");
    }

    protected function loadDependencies(): array
    {
        $file = base_path('module-dependencies.json');
        return file_exists($file) ? json_decode(file_get_contents($file), true) : [];
    }

    protected function resolveDependencies(string $module, array $deps): array
    {
        $resolved = [];
        $this->resolve($module, $deps, $resolved, []);
        return array_reverse($resolved); // Dependencies first
    }

    protected function resolve(string $module, array $deps, array &$resolved, array &$path): void
    {
        if (in_array($module, $path)) {
            throw new \Exception("Circular dependency detected: " . implode(' -> ', $path) . " -> {$module}");
        }

        if (in_array($module, $resolved)) {
            return;
        }

        $path[] = $module;

        foreach ($deps['dependencies'][$module] ?? [] as $dependency) {
            $this->resolve($dependency, $deps, $resolved, $path);
        }

        array_pop($path);
        $resolved[] = $module;
    }
}

Disabling Modules

Safe Module Deactivation

bash
# Disable a module (keeps data)
php artisan module:disable ModuleName

# Disable with confirmation prompt
php artisan module:disable ModuleName --confirm

# Disable and backup data
php artisan module:disable ModuleName --backup

Graceful Shutdown Process

php
<?php

namespace Modules\YourModule\Console\Commands;

use Illuminate\Console\Command;

class GracefulShutdownCommand extends Command
{
    protected $signature = 'module:shutdown {module}';
    protected $description = 'Gracefully shutdown a module';

    public function handle()
    {
        $moduleName = $this->argument('module');

        $this->info("Initiating graceful shutdown for {$moduleName}...");

        // 1. Stop processing new requests
        $this->stopNewRequests($moduleName);

        // 2. Wait for active processes to complete
        $this->waitForActiveProcesses($moduleName);

        // 3. Backup critical data
        $this->backupModuleData($moduleName);

        // 4. Clear module caches
        $this->clearModuleCaches($moduleName);

        // 5. Disable the module
        $this->call('module:disable', ['module' => $moduleName]);

        $this->info("✅ Module {$moduleName} shut down gracefully");
    }

    protected function stopNewRequests(string $module): void
    {
        // Set maintenance mode for module routes
        cache()->put("module.{$module}.maintenance", true, now()->addHours(1));
        $this->line("New requests blocked for {$module}");
    }

    protected function waitForActiveProcesses(string $module): void
    {
        $timeout = 30; // seconds
        $start = time();

        while ((time() - $start) < $timeout) {
            $activeJobs = $this->getActiveJobs($module);

            if ($activeJobs === 0) {
                break;
            }

            $this->line("⏳ Waiting for {$activeJobs} active processes...");
            sleep(2);
        }

        $this->line("✅ All active processes completed");
    }

    protected function backupModuleData(string $module): void
    {
        $backupPath = storage_path("backups/{$module}-" . date('Y-m-d-H-i-s') . ".sql");

        // Export module-specific data
        $this->call('module:backup', [
            'module' => $module,
            '--path' => $backupPath
        ]);

        $this->line("Data backed up to {$backupPath}");
    }

    protected function clearModuleCaches(string $module): void
    {
        cache()->tags(["module.{$module}"])->flush();
        $this->line("🧹 Module caches cleared");
    }

    protected function getActiveJobs(string $module): int
    {
        // Query active jobs for this module
        return \DB::table('jobs')
            ->where('payload', 'like', "%{$module}%")
            ->count();
    }
}

Updates & Migrations

Module Updates

Version Management

Create module-versions.json:

json
{
    "modules": {
        "PaymentGateway": {
            "current": "1.2.3",
            "available": "1.3.0",
            "changelog_url": "https://example.com/changelog",
            "update_available": true
        },
        "TaskManager": {
            "current": "2.1.0",
            "available": "2.1.0",
            "update_available": false
        }
    }
}

Update Detection Command

php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;

class CheckModuleUpdates extends Command
{
    protected $signature = 'module:check-updates {--module=} {--all}';
    protected $description = 'Check for available module updates';

    public function handle()
    {
        if ($this->option('all')) {
            $this->checkAllModules();
        } elseif ($module = $this->option('module')) {
            $this->checkModuleUpdate($module);
        } else {
            $this->checkAllModules();
        }
    }

    protected function checkAllModules(): void
    {
        $modules = \Nwidart\Modules\Facades\Module::all();
        $headers = ['Module', 'Current', 'Available', 'Status'];
        $rows = [];

        foreach ($modules as $module) {
            $updateInfo = $this->getUpdateInfo($module->getName());
            $rows[] = [
                $module->getName(),
                $updateInfo['current'] ?? 'Unknown',
                $updateInfo['available'] ?? 'Unknown',
                $updateInfo['update_available'] ? '⬆️ Update Available' : '✅ Up to date'
            ];
        }

        $this->table($headers, $rows);
    }

    protected function checkModuleUpdate(string $moduleName): void
    {
        $updateInfo = $this->getUpdateInfo($moduleName);

        if ($updateInfo['update_available']) {
            $this->info("Update available for {$moduleName}");
            $this->line("Current: {$updateInfo['current']}");
            $this->line("Available: {$updateInfo['available']}");

            if ($this->confirm('Would you like to update now?')) {
                $this->call('module:update', ['module' => $moduleName]);
            }
        } else {
            $this->info("✅ {$moduleName} is up to date");
        }
    }

    protected function getUpdateInfo(string $moduleName): array
    {
        // This would typically check a registry or update server
        try {
            $response = Http::get("https://updates.yourcompany.com/modules/{$moduleName}/latest");

            if ($response->successful()) {
                return $response->json();
            }
        } catch (\Exception $e) {
            $this->warn("Could not check updates for {$moduleName}: " . $e->getMessage());
        }

        return [
            'current' => 'Unknown',
            'available' => 'Unknown',
            'update_available' => false
        ];
    }
}

Module Update Process

bash
# Update a specific module
php artisan module:update ModuleName

# Update with backup
php artisan module:update ModuleName --backup

# Update all modules
php artisan module:update-all

# Dry run (check what would be updated)
php artisan module:update ModuleName --dry-run

Database Migrations

Migration Management

bash
# Run migrations for specific module
php artisan module:migrate ModuleName

# Rollback migrations
php artisan module:migrate-rollback ModuleName

# Check migration status
php artisan module:migrate-status ModuleName

# Refresh migrations (rollback and re-run)
php artisan module:migrate-refresh ModuleName

Safe Migration Strategy

Create migration-safety-checks.php:

php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class SafeMigrationCommand extends Command
{
    protected $signature = 'module:migrate-safe {module} {--check-only}';
    protected $description = 'Run module migrations with safety checks';

    public function handle()
    {
        $moduleName = $this->argument('module');
        $checkOnly = $this->option('check-only');

        $this->info("Running safety checks for {$moduleName} migrations...");

        $checks = [
            'Database Connection' => $this->checkDatabaseConnection(),
            'Backup Exists' => $this->checkBackupExists(),
            'Disk Space' => $this->checkDiskSpace(),
            'Active Connections' => $this->checkActiveConnections(),
            'Migration Files' => $this->validateMigrationFiles($moduleName),
        ];

        $passed = 0;
        foreach ($checks as $check => $result) {
            if ($result['status']) {
                $this->line("✅ {$check}: {$result['message']}");
                $passed++;
            } else {
                $this->error("❌ {$check}: {$result['message']}");
            }
        }

        if ($passed !== count($checks)) {
            $this->error("Safety checks failed. Migration aborted.");
            return 1;
        }

        if ($checkOnly) {
            $this->info("✅ All safety checks passed. Migration can proceed.");
            return 0;
        }

        // Proceed with migration
        if ($this->confirm('All safety checks passed. Proceed with migration?')) {
            $this->runMigrationWithMonitoring($moduleName);
        }
    }

    protected function checkDatabaseConnection(): array
    {
        try {
            DB::connection()->getPdo();
            return ['status' => true, 'message' => 'Connected'];
        } catch (\Exception $e) {
            return ['status' => false, 'message' => 'Connection failed'];
        }
    }

    protected function checkBackupExists(): array
    {
        $backupPath = storage_path('backups/pre-migration-' . date('Y-m-d') . '.sql');
        return [
            'status' => file_exists($backupPath),
            'message' => file_exists($backupPath) ? 'Recent backup found' : 'No recent backup'
        ];
    }

    protected function checkDiskSpace(): array
    {
        $freeBytes = disk_free_space(database_path());
        $freeGB = $freeBytes / (1024 * 1024 * 1024);

        return [
            'status' => $freeGB > 1, // At least 1GB free
            'message' => number_format($freeGB, 2) . 'GB available'
        ];
    }

    protected function checkActiveConnections(): array
    {
        $connections = DB::select("SHOW PROCESSLIST");
        $activeCount = count($connections);

        return [
            'status' => $activeCount < 50, // Fewer than 50 active connections
            'message' => "{$activeCount} active connections"
        ];
    }

    protected function validateMigrationFiles(string $moduleName): array
    {
        $migrationPath = base_path("Modules/{$moduleName}/Database/Migrations");

        if (!is_dir($migrationPath)) {
            return ['status' => false, 'message' => 'Migration directory not found'];
        }

        $migrations = glob($migrationPath . '/*.php');
        $valid = true;
        $issues = [];

        foreach ($migrations as $migration) {
            // Basic syntax check
            $contents = file_get_contents($migration);
            if (strpos($contents, 'Schema::') === false) {
                $valid = false;
                $issues[] = basename($migration) . ': No Schema operations found';
            }
        }

        return [
            'status' => $valid,
            'message' => $valid ? count($migrations) . ' migrations validated' : implode(', ', $issues)
        ];
    }

    protected function runMigrationWithMonitoring(string $moduleName): void
    {
        $startTime = microtime(true);
        $this->info("Starting migration for {$moduleName}...");

        try {
            // Monitor database size before
            $sizeBefore = $this->getDatabaseSize();

            // Run the migration
            $this->call('module:migrate', ['module' => $moduleName]);

            // Monitor database size after
            $sizeAfter = $this->getDatabaseSize();
            $duration = microtime(true) - $startTime;

            $this->info("✅ Migration completed successfully!");
            $this->line("Duration: " . number_format($duration, 2) . " seconds");
            $this->line("Database size change: " . $this->formatBytes($sizeAfter - $sizeBefore));

        } catch (\Exception $e) {
            $this->error("❌ Migration failed: " . $e->getMessage());
            $this->warn("Consider rolling back if necessary");
        }
    }

    protected function getDatabaseSize(): int
    {
        $result = DB::select("
            SELECT SUM(data_length + index_length) as size
            FROM information_schema.tables
            WHERE table_schema = DATABASE()
        ");

        return $result[0]->size ?? 0;
    }

    protected function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $unit = 0;

        while ($bytes > 1024 && $unit < count($units) - 1) {
            $bytes /= 1024;
            $unit++;
        }

        return number_format($bytes, 2) . ' ' . $units[$unit];
    }
}

Troubleshooting

Common Issues & Solutions

Module Won't Activate

Symptoms:

  • Module appears in list but won't enable
  • Error messages during activation
  • Module shows as installed but not active

Diagnostic Steps:

bash
# Check module status
php artisan module:list

# Validate module structure
php artisan module:validate ModuleName

# Check for missing dependencies
php artisan module:check-deps ModuleName

# View detailed error logs
tail -f storage/logs/laravel.log

# Test module service provider
php artisan tinker
>>> app()->resolveProvider('Modules\ModuleName\Providers\ModuleServiceProvider')

Common Solutions:

bash
# 1. Clear all caches
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear

# 2. Regenerate autoload files
composer dump-autoload

# 3. Check file permissions
chmod -R 755 Modules/ModuleName
chown -R www-data:www-data Modules/ModuleName

# 4. Validate composer.json
cd Modules/ModuleName
composer validate

# 5. Check for missing migrations
php artisan module:migrate-status ModuleName

Performance Issues

Create performance diagnostic command:

php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Nwidart\Modules\Facades\Module;

class ModulePerformanceDiagnostic extends Command
{
    protected $signature = 'module:diagnose-performance {module?}';
    protected $description = 'Diagnose module performance issues';

    public function handle()
    {
        $moduleName = $this->argument('module');

        if ($moduleName) {
            $this->diagnoseModule($moduleName);
        } else {
            $this->diagnoseAllModules();
        }
    }

    protected function diagnoseModule(string $moduleName): void
    {
        $this->info("Diagnosing performance for {$moduleName}...");

        $issues = [];

        // Check service provider efficiency
        $bootTime = $this->measureServiceProviderBoot($moduleName);
        if ($bootTime > 100) { // ms
            $issues[] = "Service provider boot time: {$bootTime}ms (slow)";
        }

        // Check route loading time
        $routeTime = $this->measureRouteLoading($moduleName);
        if ($routeTime > 50) { // ms
            $issues[] = "Route loading time: {$routeTime}ms (slow)";
        }

        // Check migration count and complexity
        $migrationComplexity = $this->checkMigrationComplexity($moduleName);
        if ($migrationComplexity['score'] > 50) {
            $issues[] = "Migration complexity score: {$migrationComplexity['score']} (high)";
        }

        // Check view compilation time
        $viewTime = $this->measureViewCompilation($moduleName);
        if ($viewTime > 200) { // ms
            $issues[] = "View compilation time: {$viewTime}ms (slow)";
        }

        // Check memory usage
        $memoryUsage = $this->measureMemoryUsage($moduleName);
        if ($memoryUsage > 10) { // MB
            $issues[] = "Memory usage: {$memoryUsage}MB (high)";
        }

        if (empty($issues)) {
            $this->info("✅ No performance issues detected for {$moduleName}");
        } else {
            $this->warn("⚠️ Performance issues found:");
            foreach ($issues as $issue) {
                $this->line("  - {$issue}");
            }

            $this->line("\nRecommendations:");
            $this->suggestOptimizations($issues);
        }
    }

    protected function measureServiceProviderBoot(string $moduleName): float
    {
        $start = microtime(true);

        try {
            $provider = app()->resolveProvider("Modules\\{$moduleName}\\Providers\\{$moduleName}ServiceProvider");
            $provider->boot();
        } catch (\Exception $e) {
            return 0;
        }

        return (microtime(true) - $start) * 1000;
    }

    protected function measureRouteLoading(string $moduleName): float
    {
        $routePath = base_path("Modules/{$moduleName}/Routes");

        if (!is_dir($routePath)) {
            return 0;
        }

        $start = microtime(true);

        $files = glob($routePath . '/*.php');
        foreach ($files as $file) {
            include_once $file;
        }

        return (microtime(true) - $start) * 1000;
    }

    protected function checkMigrationComplexity(string $moduleName): array
    {
        $migrationPath = base_path("Modules/{$moduleName}/Database/Migrations");

        if (!is_dir($migrationPath)) {
            return ['score' => 0, 'details' => []];
        }

        $migrations = glob($migrationPath . '/*.php');
        $totalScore = 0;
        $details = [];

        foreach ($migrations as $migration) {
            $content = file_get_contents($migration);
            $score = 0;

            // Count complex operations
            $score += substr_count($content, 'foreign(') * 5;
            $score += substr_count($content, 'index(') * 2;
            $score += substr_count($content, 'unique(') * 3;
            $score += substr_count($content, 'dropColumn') * 4;
            $score += substr_count($content, 'renameColumn') * 4;

            $totalScore += $score;

            if ($score > 20) {
                $details[] = basename($migration) . ": score {$score}";
            }
        }

        return ['score' => $totalScore, 'details' => $details];
    }

    protected function measureViewCompilation(string $moduleName): float
    {
        $viewPath = base_path("Modules/{$moduleName}/Resources/views");

        if (!is_dir($viewPath)) {
            return 0;
        }

        $start = microtime(true);

        // Find and compile view files
        $views = $this->findViewFiles($viewPath);
        foreach ($views as $view) {
            try {
                view($view)->render();
            } catch (\Exception $e) {
                // Skip views that can't be rendered without data
                continue;
            }
        }

        return (microtime(true) - $start) * 1000;
    }

    protected function findViewFiles(string $path): array
    {
        $views = [];
        $files = glob($path . '/**/*.blade.php', GLOB_BRACE);

        foreach ($files as $file) {
            $relativePath = str_replace([$path . '/', '.blade.php'], '', $file);
            $views[] = str_replace('/', '.', $relativePath);
        }

        return $views;
    }

    protected function measureMemoryUsage(string $moduleName): float
    {
        $beforeMemory = memory_get_usage(true);

        // Load module components
        $module = Module::find($moduleName);
        if ($module) {
            $module->boot();
        }

        $afterMemory = memory_get_usage(true);

        return ($afterMemory - $beforeMemory) / (1024 * 1024); // Convert to MB
    }

    protected function suggestOptimizations(array $issues): void
    {
        foreach ($issues as $issue) {
            if (strpos($issue, 'Service provider') !== false) {
                $this->line("  → Consider lazy loading services and deferred providers");
            }

            if (strpos($issue, 'Route loading') !== false) {
                $this->line("  → Use route caching: php artisan route:cache");
            }

            if (strpos($issue, 'Migration complexity') !== false) {
                $this->line("  → Split complex migrations into smaller ones");
            }

            if (strpos($issue, 'View compilation') !== false) {
                $this->line("  → Use view caching: php artisan view:cache");
            }

            if (strpos($issue, 'Memory usage') !== false) {
                $this->line("  → Optimize service providers and reduce eager loading");
            }
        }
    }

    protected function diagnoseAllModules(): void
    {
        $modules = Module::all();

        foreach ($modules as $module) {
            if ($module->isEnabled()) {
                $this->diagnoseModule($module->getName());
                $this->line('');
            }
        }
    }
}

Database Issues

Common Database Problems:

bash
# Check for orphaned module data
php artisan module:check-orphans ModuleName

# Repair module database integrity
php artisan module:repair-db ModuleName

# Verify foreign key constraints
php artisan module:check-constraints ModuleName

Database repair command:

php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class ModuleDatabaseRepair extends Command
{
    protected $signature = 'module:repair-db {module} {--dry-run}';
    protected $description = 'Repair module database issues';

    public function handle()
    {
        $moduleName = $this->argument('module');
        $dryRun = $this->option('dry-run');

        $this->info("Analyzing database for {$moduleName}...");

        $issues = $this->detectIssues($moduleName);

        if (empty($issues)) {
            $this->info("✅ No database issues found for {$moduleName}");
            return;
        }

        $this->warn("Found " . count($issues) . " database issues:");
        foreach ($issues as $issue) {
            $this->line("  - {$issue['description']}");
        }

        if ($dryRun) {
            $this->info("Dry run mode - no changes made");
            return;
        }

        if ($this->confirm('Attempt to repair these issues?')) {
            $this->repairIssues($issues);
        }
    }

    protected function detectIssues(string $moduleName): array
    {
        $issues = [];

        // Check for missing tables
        $expectedTables = $this->getExpectedTables($moduleName);
        foreach ($expectedTables as $table) {
            if (!Schema::hasTable($table)) {
                $issues[] = [
                    'type' => 'missing_table',
                    'table' => $table,
                    'description' => "Missing table: {$table}"
                ];
            }
        }

        // Check for orphaned records
        $orphanedRecords = $this->findOrphanedRecords($moduleName);
        foreach ($orphanedRecords as $record) {
            $issues[] = [
                'type' => 'orphaned_record',
                'table' => $record['table'],
                'count' => $record['count'],
                'description' => "Orphaned records in {$record['table']}: {$record['count']}"
            ];
        }

        // Check for broken foreign keys
        $brokenForeignKeys = $this->findBrokenForeignKeys($moduleName);
        foreach ($brokenForeignKeys as $fk) {
            $issues[] = [
                'type' => 'broken_foreign_key',
                'table' => $fk['table'],
                'column' => $fk['column'],
                'description' => "Broken foreign key: {$fk['table']}.{$fk['column']}"
            ];
        }

        return $issues;
    }

    protected function getExpectedTables(string $moduleName): array
    {
        // Parse migration files to determine expected tables
        $migrationPath = base_path("Modules/{$moduleName}/Database/Migrations");
        $tables = [];

        if (!is_dir($migrationPath)) {
            return $tables;
        }

        $migrations = glob($migrationPath . '/*.php');

        foreach ($migrations as $migration) {
            $content = file_get_contents($migration);

            // Extract table names from Schema::create calls
            preg_match_all('/Schema::create\([\'"]([^\'"]+)[\'"]/', $content, $matches);
            $tables = array_merge($tables, $matches[1]);
        }

        return array_unique($tables);
    }

    protected function findOrphanedRecords(string $moduleName): array
    {
        $orphaned = [];

        // This is a simplified example - you'd implement specific logic
        // based on your module's data relationships

        return $orphaned;
    }

    protected function findBrokenForeignKeys(string $moduleName): array
    {
        $broken = [];

        // Check foreign key constraints
        $tables = $this->getExpectedTables($moduleName);

        foreach ($tables as $table) {
            if (!Schema::hasTable($table)) {
                continue;
            }

            // Get foreign key information
            $foreignKeys = DB::select("
                SELECT
                    COLUMN_NAME,
                    REFERENCED_TABLE_NAME,
                    REFERENCED_COLUMN_NAME
                FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
                WHERE TABLE_SCHEMA = DATABASE()
                AND TABLE_NAME = ?
                AND REFERENCED_TABLE_NAME IS NOT NULL
            ", [$table]);

            foreach ($foreignKeys as $fk) {
                // Check if referenced table exists
                if (!Schema::hasTable($fk->REFERENCED_TABLE_NAME)) {
                    $broken[] = [
                        'table' => $table,
                        'column' => $fk->COLUMN_NAME,
                        'referenced_table' => $fk->REFERENCED_TABLE_NAME
                    ];
                }
            }
        }

        return $broken;
    }

    protected function repairIssues(array $issues): void
    {
        $repaired = 0;

        foreach ($issues as $issue) {
            try {
                switch ($issue['type']) {
                    case 'missing_table':
                        $this->repairMissingTable($issue);
                        break;

                    case 'orphaned_record':
                        $this->repairOrphanedRecords($issue);
                        break;

                    case 'broken_foreign_key':
                        $this->repairBrokenForeignKey($issue);
                        break;
                }

                $this->line("✅ Repaired: {$issue['description']}");
                $repaired++;

            } catch (\Exception $e) {
                $this->error("❌ Failed to repair: {$issue['description']} - {$e->getMessage()}");
            }
        }

        $this->info("Repaired {$repaired} of " . count($issues) . " issues");
    }

    protected function repairMissingTable(array $issue): void
    {
        // Re-run migrations for missing tables
        $this->call('module:migrate', ['module' => $this->argument('module')]);
    }

    protected function repairOrphanedRecords(array $issue): void
    {
        // Remove or fix orphaned records based on business logic
        DB::table($issue['table'])
            ->whereNull('tenant_id') // Example condition
            ->delete();
    }

    protected function repairBrokenForeignKey(array $issue): void
    {
        // This would require careful analysis of the specific foreign key issue
        $this->warn("Manual intervention required for foreign key: {$issue['table']}.{$issue['column']}");
    }
}

Monitoring & Health Checks

Module Health Dashboard

Create a comprehensive health monitoring system:

php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Nwidart\Modules\Facades\Module;

class ModuleHealthCheck extends Command
{
    protected $signature = 'module:health {--format=table} {--module=}';
    protected $description = 'Check the health status of all modules';

    public function handle()
    {
        $format = $this->option('format');
        $specificModule = $this->option('module');

        if ($specificModule) {
            $this->checkModuleHealth($specificModule, true);
        } else {
            $this->checkAllModulesHealth($format);
        }
    }

    protected function checkAllModulesHealth(string $format): void
    {
        $modules = Module::all();
        $healthData = [];

        foreach ($modules as $module) {
            $health = $this->checkModuleHealth($module->getName(), false);
            $healthData[] = [
                'Module' => $module->getName(),
                'Status' => $module->isEnabled() ? '✅ Active' : '❌ Disabled',
                'Health' => $this->formatHealthScore($health['score']),
                'Issues' => count($health['issues']),
                'Last Check' => now()->format('H:i:s')
            ];
        }

        if ($format === 'json') {
            $this->line(json_encode($healthData, JSON_PRETTY_PRINT));
        } else {
            $this->table(['Module', 'Status', 'Health', 'Issues', 'Last Check'], $healthData);
        }
    }

    protected function checkModuleHealth(string $moduleName, bool $detailed = false): array
    {
        $issues = [];
        $score = 100;

        // Check 1: Module is properly installed
        $module = Module::find($moduleName);
        if (!$module) {
            $issues[] = 'Module not found';
            $score -= 50;
        }

        // Check 2: Service provider exists and loads
        if (!$this->checkServiceProvider($moduleName)) {
            $issues[] = 'Service provider issues';
            $score -= 20;
        }

        // Check 3: Database tables exist
        if (!$this->checkDatabaseTables($moduleName)) {
            $issues[] = 'Database table issues';
            $score -= 15;
        }

        // Check 4: Required files exist
        if (!$this->checkRequiredFiles($moduleName)) {
            $issues[] = 'Missing required files';
            $score -= 10;
        }

        // Check 5: Configuration is valid
        if (!$this->checkConfiguration($moduleName)) {
            $issues[] = 'Configuration issues';
            $score -= 5;
        }

        // Check 6: Dependencies are met
        $depIssues = $this->checkDependencies($moduleName);
        if (!empty($depIssues)) {
            $issues = array_merge($issues, $depIssues);
            $score -= count($depIssues) * 3;
        }

        // Check 7: Performance metrics
        $perfIssues = $this->checkPerformance($moduleName);
        if (!empty($perfIssues)) {
            $issues = array_merge($issues, $perfIssues);
            $score -= count($perfIssues) * 2;
        }

        $score = max(0, $score);

        if ($detailed) {
            $this->displayDetailedHealth($moduleName, $score, $issues);
        }

        return ['score' => $score, 'issues' => $issues];
    }

    protected function checkServiceProvider(string $moduleName): bool
    {
        try {
            $providerClass = "Modules\\{$moduleName}\\Providers\\{$moduleName}ServiceProvider";

            if (!class_exists($providerClass)) {
                return false;
            }

            $provider = app()->resolveProvider($providerClass);
            return $provider !== null;

        } catch (\Exception $e) {
            return false;
        }
    }

    protected function checkDatabaseTables(string $moduleName): bool
    {
        try {
            $expectedTables = $this->getExpectedTables($moduleName);

            foreach ($expectedTables as $table) {
                if (!\Schema::hasTable($table)) {
                    return false;
                }
            }

            return true;

        } catch (\Exception $e) {
            return false;
        }
    }

    protected function checkRequiredFiles(string $moduleName): bool
    {
        $requiredFiles = [
            "Modules/{$moduleName}/module.json",
            "Modules/{$moduleName}/composer.json",
            "Modules/{$moduleName}/Providers/{$moduleName}ServiceProvider.php"
        ];

        foreach ($requiredFiles as $file) {
            if (!file_exists(base_path($file))) {
                return false;
            }
        }

        return true;
    }

    protected function checkConfiguration(string $moduleName): bool
    {
        try {
            $moduleJsonPath = base_path("Modules/{$moduleName}/module.json");

            if (!file_exists($moduleJsonPath)) {
                return false;
            }

            $moduleConfig = json_decode(file_get_contents($moduleJsonPath), true);

            if (!$moduleConfig || !isset($moduleConfig['name'])) {
                return false;
            }

            return true;

        } catch (\Exception $e) {
            return false;
        }
    }

    protected function checkDependencies(string $moduleName): array
    {
        $issues = [];

        try {
            $composerPath = base_path("Modules/{$moduleName}/composer.json");

            if (file_exists($composerPath)) {
                $composer = json_decode(file_get_contents($composerPath), true);

                if (isset($composer['require'])) {
                    foreach ($composer['require'] as $package => $version) {
                        if (!$this->isPackageInstalled($package)) {
                            $issues[] = "Missing dependency: {$package}";
                        }
                    }
                }
            }

        } catch (\Exception $e) {
            $issues[] = "Dependency check failed: " . $e->getMessage();
        }

        return $issues;
    }

    protected function checkPerformance(string $moduleName): array
    {
        $issues = [];

        // Memory usage check
        $memoryBefore = memory_get_usage(true);

        try {
            $module = Module::find($moduleName);
            if ($module && $module->isEnabled()) {
                // Simulate module operations
                $module->boot();
            }
        } catch (\Exception $e) {
            $issues[] = "Performance test failed: " . $e->getMessage();
        }

        $memoryAfter = memory_get_usage(true);
        $memoryUsage = ($memoryAfter - $memoryBefore) / (1024 * 1024); // MB

        if ($memoryUsage > 10) { // More than 10MB
            $issues[] = "High memory usage: " . number_format($memoryUsage, 2) . "MB";
        }

        return $issues;
    }

    protected function displayDetailedHealth(string $moduleName, int $score, array $issues): void
    {
        $this->info("Health Report for {$moduleName}");
        $this->line("Overall Score: {$this->formatHealthScore($score)}");

        if (empty($issues)) {
            $this->info("✅ No issues found - module is healthy!");
        } else {
            $this->warn("Issues found:");
            foreach ($issues as $issue) {
                $this->line("  - {$issue}");
            }
        }

        $this->line('');
        $this->displayHealthRecommendations($score, $issues);
    }

    protected function formatHealthScore(int $score): string
    {
        if ($score >= 90) return "🟢 {$score}% (Excellent)";
        if ($score >= 70) return "🟡 {$score}% (Good)";
        if ($score >= 50) return "🟠 {$score}% (Fair)";
        return "{$score}% (Poor)";
    }

    protected function displayHealthRecommendations(int $score, array $issues): void
    {
        $this->info("Recommendations:");

        if ($score < 50) {
            $this->line("  - Critical issues detected - immediate attention required");
            $this->line("  - Consider disabling module until issues are resolved");
        } elseif ($score < 70) {
            $this->line("  - Address identified issues to improve stability");
            $this->line("  - Monitor module performance closely");
        } elseif ($score < 90) {
            $this->line("  - Minor optimizations recommended");
            $this->line("  - Module is generally healthy");
        } else {
            $this->line("  - Module is in excellent health!");
            $this->line("  - Continue current maintenance practices");
        }

        if (in_array('High memory usage', array_map(fn($i) => explode(':', $i)[0], $issues))) {
            $this->line("  - Optimize memory usage in service providers");
        }

        if (in_array('Missing dependency', array_map(fn($i) => explode(':', $i)[0], $issues))) {
            $this->line("  - Run: composer install in module directory");
        }
    }

    protected function getExpectedTables(string $moduleName): array
    {
        // Implementation similar to previous examples
        return [];
    }

    protected function isPackageInstalled(string $package): bool
    {
        try {
            return app()->bound($package) || class_exists($package);
        } catch (\Exception $e) {
            return false;
        }
    }
}

This comprehensive module management guide provides all the tools and techniques needed to effectively manage WhatsmarkSaaS modules throughout their entire lifecycle. From installation and activation to troubleshooting and performance monitoring, these practices ensure your modules remain stable, secure, and performant in production environments.

© 2024 - Corbital Technologies. All rights reserved.