Skip to content

Model Relationships

The Module Manager provides a powerful system for establishing relationships between your module models and the core application models (Users and Tenants). This document explains how to implement and use these relationships.

Overview

Module Manager uses traits to create bi-directional relationships between:

  1. User models and module-specific models
  2. Tenant models and module-specific models (when multi-tenancy is enabled)

This approach keeps your code organized and ensures proper relationship handling across the entire application.

Relationship Traits

Each module should define two relationship traits:

  1. InteractsWithYourModule - Used by the User model
  2. TenantInteractsWithYourModule - Used by the Tenant model (when multi-tenancy is enabled)

These traits are then automatically included in the corresponding application-level traits, which are configured in module-manager.php:

php
// config/module-manager.php
return [
    // ...
    'user_module_interaction_trait' => 'App\Traits\UserInteractsWithModules',
    'tenant_module_interaction_trait' => 'App\Traits\TenantInteractsWithModules',
    // ...
];

Creating Module Interaction Traits

User Interaction Trait

Create a trait in your module that defines relationships between users and your module's models:

php
<?php

declare(strict_types = 1);

namespace Modules\TaskManager\Traits;

use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Modules\TaskManager\Models\Task;
use Modules\TaskManager\Models\TaskList;

trait InteractsWithTaskManager
{
    /**
     * Get tasks created by this user
     */
    public function tasks(): HasMany
    {
        return $this->hasMany(Task::class, 'created_by');
    }
    
    /**
     * Get task lists owned by this user
     */
    public function taskLists(): HasMany
    {
        return $this->hasMany(TaskList::class, 'owner_id');
    }
    
    /**
     * Get tasks assigned to this user
     */
    public function assignedTasks(): BelongsToMany
    {
        // Using the prefix from configuration
        $tableName = config('taskmanager.table_prefix') . 'task_assignments';
        
        return $this->belongsToMany(
            Task::class,
            $tableName,
            userIdColumn(), // Helper function to get the user ID column name
            'task_id'
        )->withTimestamps();
    }
    
    /**
     * Check if the user has any overdue tasks
     */
    public function hasOverdueTasks(): bool
    {
        return $this->assignedTasks()
            ->where('due_date', '<', now())
            ->where('completed', false)
            ->exists();
    }
}

Tenant Interaction Trait

When multi-tenancy is enabled, create a trait for tenant relationships:

php
<?php

declare(strict_types = 1);

namespace Modules\TaskManager\Traits;

use Illuminate\Database\Eloquent\Relations\HasMany;
use Modules\TaskManager\Models\Task;
use Modules\TaskManager\Models\TaskList;
use Modules\TaskManager\Models\TaskTemplate;

trait TenantInteractsWithTaskManager
{
    /**
     * Get all tasks belonging to this tenant
     */
    public function tasks(): HasMany
    {
        return $this->hasMany(Task::class, tenantIdColumn());
    }
    
    /**
     * Get all task lists for this tenant
     */
    public function taskLists(): HasMany
    {
        return $this->hasMany(TaskList::class, tenantIdColumn());
    }
    
    /**
     * Get task templates defined for this tenant
     */
    public function taskTemplates(): HasMany
    {
        return $this->hasMany(TaskTemplate::class, tenantIdColumn());
    }
    
    /**
     * Get a count of active tasks for this tenant
     */
    public function activeTasks()
    {
        return $this->tasks()->where('completed', false)->count();
    }
}

Using the Module Traits in Application Models

After creating the module-specific traits, they need to be used in the application model traits:

UserInteractsWithModules Trait

php
<?php

declare(strict_types = 1);

namespace App\Traits;

use Modules\TaskManager\Traits\InteractsWithTaskManager;
use Modules\Analytics\Traits\InteractsWithAnalytics;
use Modules\Documents\Traits\InteractsWithDocuments;
// Import additional module traits as needed

trait UserInteractsWithModules
{
    // Include all module interaction traits
    use InteractsWithTaskManager;
    use InteractsWithAnalytics;
    use InteractsWithDocuments;
    // Use additional traits as needed
}

TenantInteractsWithModules Trait

php
<?php

declare(strict_types = 1);

namespace App\Traits;

use Modules\TaskManager\Traits\TenantInteractsWithTaskManager;
use Modules\Analytics\Traits\TenantInteractsWithAnalytics;
use Modules\Documents\Traits\TenantInteractsWithDocuments;
// Import additional module traits as needed

trait TenantInteractsWithModules
{
    // Include all tenant interaction traits
    use TenantInteractsWithTaskManager;
    use TenantInteractsWithAnalytics;
    use TenantInteractsWithDocuments;
    // Use additional traits as needed
}

Automatic Verification

Module Manager automatically verifies that module traits are properly included in the application model traits. If you install a new module and forget to add its traits, you'll receive a warning if you're in a development environment.

This verification occurs when the application boots, checking if the traits defined in the module are properly included in the application-level traits.

Helper Functions

Module Manager provides helper functions to ensure consistent relationship definitions:

  • userIdColumn() - Returns the configured user ID column name
  • tenantIdColumn() - Returns the configured tenant ID column name
  • isMultiTenant() - Checks if multi-tenancy is enabled
  • tenantRelationshipName() - Returns the relationship name for the tenant (based on the class name)

Best Practices

  1. Naming Conventions:

    • User traits should be named InteractsWith{ModuleName}
    • Tenant traits should be named TenantInteractsWith{ModuleName}
  2. Relationship Types:

    • Use HasMany for one-to-many relationships (a user has many tasks)
    • Use BelongsToMany for many-to-many relationships (users assigned to tasks)
    • Use HasOne for one-to-one relationships (user has one profile)
  3. Column Naming:

    • Use the helper functions (userIdColumn(), tenantIdColumn()) for foreign key references
    • Use specific column names for semantic relationships (e.g., created_by, owner_id)
  4. Table Prefixes:

    • Access the module's table prefix using config('{modulename}.table_prefix')
    • This ensures consistency if the prefix is changed in configuration
  5. Additional Methods:

    • Include convenience methods (like hasOverdueTasks()) that build on the relationships
    • Use accessor methods for computed properties when appropriate

Multi-Tenancy Considerations

When multi-tenancy is enabled, follow these additional guidelines:

  1. Always include tenant ID in pivots and relationships where appropriate
  2. Use the mergeTenantId() helper function when creating or updating related models
  3. Consider using the HasTenant trait on your module models to automatically apply tenant scoping

Example: Using Relationships in Controllers

Here's how you might use these relationships in a controller:

php
public function dashboard()
{
    // Get the current user's tasks
    $tasks = auth()->user()->tasks()->latest()->take(5)->get();
    
    // Get tasks for the current tenant (in multi-tenant mode)
    $allTeamTasks = tenant()->tasks()->count();
    
    // Use a computed property/method
    $hasOverdueTasks = auth()->user()->hasOverdueTasks();
    
    return view('taskmanager::dashboard', compact('tasks', 'allTeamTasks', 'hasOverdueTasks'));
}