Appearance
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:
- User models and module-specific models
- 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:
InteractsWithYourModule
- Used by the User modelTenantInteractsWithYourModule
- 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 nametenantIdColumn()
- Returns the configured tenant ID column nameisMultiTenant()
- Checks if multi-tenancy is enabledtenantRelationshipName()
- Returns the relationship name for the tenant (based on the class name)
Best Practices
Naming Conventions:
- User traits should be named
InteractsWith{ModuleName}
- Tenant traits should be named
TenantInteractsWith{ModuleName}
- User traits should be named
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)
- Use
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
)
- Use the helper functions (
Table Prefixes:
- Access the module's table prefix using
config('{modulename}.table_prefix')
- This ensures consistency if the prefix is changed in configuration
- Access the module's table prefix using
Additional Methods:
- Include convenience methods (like
hasOverdueTasks()
) that build on the relationships - Use accessor methods for computed properties when appropriate
- Include convenience methods (like
Multi-Tenancy Considerations
When multi-tenancy is enabled, follow these additional guidelines:
- Always include tenant ID in pivots and relationships where appropriate
- Use the
mergeTenantId()
helper function when creating or updating related models - 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'));
}