Link System

Cross-module relationships without tight coupling

Overview

The Link System allows modules to define relationships between each other without creating hard dependencies. This keeps domain modules decoupled while still enabling rich cross-module functionality.

The Problem

Without links, modules would need to import each other directly:

// ❌ BAD: Products module directly imports Tax module
namespace Modules\Products\Models;

use Modules\Tax\Models\TaxClass; // Tight coupling!

class Product extends Model
{
    public function taxClass(): BelongsTo
    {
        return $this->belongsTo(TaxClass::class);
    }
}

This creates problems:

  • Products now depends on Tax
  • Can’t disable Tax without breaking Products
  • Circular dependencies become likely

Links define relationships externally:

// ✅ GOOD: Link module handles the relationship
namespace Modules\TaxProductLink\Links;

use Esegments\ModularArchitecture\Facades\Links;

Links::define('TaxProductLink')
    ->requires('Tax', 'Products')
    ->belongsTo(Product::class, 'taxClass', TaxClass::class, 'tax_class_id')
    ->hasMany(TaxClass::class, 'products', Product::class, 'tax_class_id');

Using Fluent API

use Esegments\ModularArchitecture\Facades\Links;

Links::define('TaxProductLink')
    // Required modules - link only activates when both exist
    ->requires('Tax', 'Products')
    
    // Add belongsTo relationship to Product
    ->belongsTo(
        Product::class,      // Model to add relationship to
        'taxClass',          // Relationship name
        TaxClass::class,     // Related model
        'tax_class_id'       // Foreign key
    )
    
    // Add hasMany relationship to TaxClass
    ->hasMany(
        TaxClass::class,
        'products',
        Product::class,
        'tax_class_id'
    );

Create in app/Links/ directory:

Modules/TaxProductLink/
└── app/
    └── Links/
        └── TaxProductLink.php
namespace Modules\TaxProductLink\Links;

use Esegments\ModularArchitecture\Links\LinkDefinition;

class TaxProductLink extends LinkDefinition
{
    protected array $requires = ['Tax', 'Products'];

    public function define(): void
    {
        $this->belongsTo(Product::class, 'taxClass', TaxClass::class, 'tax_class_id');
        $this->hasMany(TaxClass::class, 'products', Product::class, 'tax_class_id');
    }
}

Available Relationship Types

belongsTo

->belongsTo(Product::class, 'taxClass', TaxClass::class, 'tax_class_id')

hasMany

->hasMany(TaxClass::class, 'products', Product::class, 'tax_class_id')

hasOne

->hasOne(User::class, 'profile', Profile::class, 'user_id')

belongsToMany

->belongsToMany(
    Product::class, 
    'categories', 
    Category::class, 
    'category_product',  // Pivot table
    'product_id',        // Foreign pivot key
    'category_id'        // Related pivot key
)

Adding Macros

Links can add macros to models:

Links::define('TaxProductLink')
    ->requires('Tax', 'Products')
    
    // Add accessor macro
    ->macro(Product::class, 'taxClassName', function () {
        return $this->taxClass?->name;
    })
    
    // Add query scope macro
    ->macro(Product::class, 'scopeWithTax', function ($query) {
        return $query->whereNotNull('tax_class_id');
    });

Usage:

$product->taxClassName;
Product::withTax()->get();

Conditional Activation

Links only activate when all required modules are enabled:

Links::define('TaxProductLink')
    ->requires('Tax', 'Products');  // Both must be enabled

If Tax module is disabled:

  • Link doesn’t boot
  • Relationships aren’t added
  • No errors thrown
use Esegments\ModularArchitecture\Facades\Links;

// Get all registered links
Links::all();

// Get only enabled links
Links::enabled();

// Get disabled links
Links::disabled();

// Check specific link
Links::has('TaxProductLink');
Links::isEnabled('TaxProductLink');

CLI Commands

# List all links
php artisan modular:links

# Check link status
php artisan modular:links:status TaxProductLink

Best Practices

Links should only define relationships, not business logic:

// ✅ Good
->belongsTo(Product::class, 'taxClass', TaxClass::class)

// ❌ Bad - business logic in link
->macro(Product::class, 'calculateTax', function () {
    return $this->price * $this->taxClass->rate / 100;
})

2. Use Descriptive Names

// ✅ Good
Links::define('TaxProductLink')
Links::define('CustomerOrderLink')

// ❌ Bad
Links::define('Link1')
Links::define('TPL')

3. Document Requirements

Links::define('TaxProductLink')
    ->requires('Tax', 'Products')
    ->description('Adds tax class relationship to products');