Module Dependencies

AutoCom's module system includes robust dependency management that ensures modules load in the correct order and fail gracefully when dependencies are missing.

How Dependencies Work

When the application boots, the ModuleLoaderService:

  1. Discovers all modules in the /modules directory
  2. Builds a dependency graph from module.json files
  3. Validates that all dependencies are available and enabled
  4. Detects circular dependencies
  5. Sorts modules topologically (dependencies load first)
  6. Loads modules in the calculated order

Declaring Dependencies

Dependencies are declared in your module.json file:

{
  "name": "StoreShopify",
  "alias": "store-shopify",
  "dependencies": {
    "modules": ["Core", "Orders"],
    "backend": {
      "php": "^8.2",
      "packages": ["guzzlehttp/guzzle"]
    },
    "frontend": {
      "packages": ["@shopify/polaris"]
    }
  }
}

Module Dependencies

The dependencies.modules array lists other AutoCom modules that must be loaded before this module:

{
  "dependencies": {
    "modules": ["Core", "Orders", "Products"]
  }
}

Rules:

  • Dependencies are loaded in order (Core → Orders → Products → YourModule)
  • If a dependency is missing or disabled, the module won't load
  • Core modules with missing dependencies cause fatal errors
  • Non-core modules are skipped with a warning

Backend Dependencies

Specify PHP version and Composer packages:

{
  "dependencies": {
    "backend": {
      "php": "^8.2",
      "packages": [
        "guzzlehttp/guzzle",
        "league/csv"
      ]
    }
  }
}

When you enable a module, the system will:

  1. Check if the PHP version requirement is met
  2. Check if declared Composer packages are installed
  3. Offer to install missing packages automatically

Frontend Dependencies

Specify npm packages:

{
  "dependencies": {
    "frontend": {
      "packages": [
        "@tanstack/react-query",
        "recharts"
      ]
    }
  }
}

When you enable a module, the system will:

  1. Check if declared npm packages are installed
  2. Offer to install missing packages automatically

Package Dependency Management

Checking Package Dependencies

Use the module:packages command to check all package dependencies:

# Check all enabled modules
php artisan module:packages

# Check a specific module
php artisan module:packages --module=StoreShopify

# Output as JSON
php artisan module:packages --json

Example output:

Package Dependency Report

PHP Version: 8.2.15

+----------------+-----+------------------+-------------+--------+
| Module         | PHP | Composer Missing | npm Missing | Status |
+----------------+-----+------------------+-------------+--------+
| Core           | ✓   | 0                | 0           | OK     |
| Orders         | ✓   | 0                | 0           | OK     |
| StoreShopify   | ✓   | 1                | 1           | MISSING|
+----------------+-----+------------------+-------------+--------+

Missing Packages:

StoreShopify:
  composer: guzzlehttp/guzzle
  npm: @shopify/polaris

To install all missing packages:
  composer require guzzlehttp/guzzle
  cd frontend && npm install @shopify/polaris

Or run: php artisan module:packages --install

Installing Missing Packages

Automatically install all missing packages:

php artisan module:packages --install

Install when enabling a module:

# Will prompt to install if packages are missing
php artisan module:enable StoreShopify

# Automatically install without prompting
php artisan module:enable StoreShopify --install-packages

# Skip package check entirely
php artisan module:enable StoreShopify --skip-packages

How Package Detection Works

The system checks:

  1. Composer packages: Reads composer.lock in the backend directory
  2. npm packages: Reads package-lock.json in the frontend directory
  3. PHP version: Compares against PHP_VERSION constant

This means packages must be properly installed (not just listed in package.json/composer.json).


Dependency Validation

Missing Dependencies

If a module declares a dependency that isn't available:

[ERROR] Module 'StoreShopify' requires module 'Orders' which is not available or not enabled

For core modules: This is a fatal error and the application won't start.

For non-core modules: The module is skipped and a warning is logged.

Circular Dependencies

The system detects circular dependencies:

ModuleA depends on ModuleB
ModuleB depends on ModuleC
ModuleC depends on ModuleA  ← Circular!

This produces:

[CRITICAL] Circular dependency detected: ModuleA -> ModuleB -> ModuleC -> ModuleA

Resolution: Refactor your modules to remove the circular reference. Consider:

  • Moving shared code to Core
  • Creating a new shared module
  • Using events instead of direct dependencies

Dependency Graph

Viewing the Graph

Use the CLI to visualize dependencies:

php artisan module:list --graph

Output:

Dependency Graph:

  Core
    └── (no dependencies)
  Orders
    └── Core
  Products
    └── Core
  Customers
    └── Core
  StoreShopify
    └── Core, Orders
  Workflows
    └── Core

Reverse Dependencies (dependents):

  Core
    └── used by: Orders, Products, Customers, StoreShopify, Workflows
  Orders
    └── used by: StoreShopify

Programmatic Access

Access the dependency graph in code:

use App\Core\Services\ModuleLoaderService;

$loader = app(ModuleLoaderService::class);

// Get the dependency graph
$graph = $loader->getDependencyGraph();
// ['Orders' => ['Core'], 'StoreShopify' => ['Core', 'Orders'], ...]

// Get all dependencies of a module (recursive)
$allDeps = $loader->getAllDependencies('StoreShopify');
// ['Core', 'Orders']

// Get modules that depend on this module
$dependents = $loader->getDependents('Core');
// ['Orders', 'Products', 'Customers', 'StoreShopify', 'Workflows']

Load Order

Modules are loaded using topological sorting (Kahn's algorithm):

  1. Modules with no dependencies load first
  2. Within the same "level", modules sort by priority
  3. Lower priority number = loads earlier

Example Load Order

Given these modules:

Module Priority Dependencies
Core 0 -
Orders 10 Core
Products 15 Core
StoreShopify 50 Core, Orders

Load order: Core → Orders → Products → StoreShopify

Debugging Load Order

Check the logs to see the calculated order:

ModuleLoader: Calculated load order ["Core", "Orders", "Products", "StoreShopify"]

Best Practices

1. Always Depend on Core

Unless your module is Core itself, always include it:

{
  "dependencies": {
    "modules": ["Core"]
  }
}

2. Minimize Dependencies

Only declare modules you directly use:

// ❌ Don't include transitive dependencies
{
  "dependencies": {
    "modules": ["Core", "Orders", "Products", "Customers"]
  }
}

// ✅ Only direct dependencies
{
  "dependencies": {
    "modules": ["Orders"]  // Orders already depends on Core
  }
}

3. Use Appropriate Priority

Type Recommended Priority
Core infrastructure 0-10
Business logic (Orders, Products) 10-30
Reseller modules 30-50
Store integrations 50-70
Channel integrations 70-90
AI/Automation 90-100

4. Document Dependencies

Add comments in your service provider:

/**
 * StoreShopify Module
 *
 * Depends on:
 * - Core: Base models and services
 * - Orders: Order creation and management
 */
class StoreShopifyServiceProvider extends ServiceProvider
{
    // ...
}

5. Use Contracts for Loose Coupling

Instead of depending on concrete modules, use contracts:

// In your module
use App\Core\Contracts\StoreProviderContract;

class MyService
{
    public function __construct(
        private StoreProviderContract $store
    ) {}
}

This allows your module to work with any store provider (Shopify, WooCommerce, etc.).


Handling Optional Dependencies

For features that enhance functionality when a module is present:

use App\Core\Services\ModuleLoaderService;

class MyService
{
    public function doSomething()
    {
        $loader = app(ModuleLoaderService::class);

        // Check if optional module is loaded
        if ($loader->isModuleLoaded('Workflows')) {
            // Use workflow features
            $this->triggerWorkflow();
        }
    }
}

Or use class existence check:

if (class_exists(\Modules\Workflows\Services\WorkflowEngine::class)) {
    // Workflow module is available
}

Next Steps