CMS Framework - v1.0.0

Developer Guide

This guide provides advanced information for developers who want to extend, customize, or build upon the CMS Framework. It covers architecture patterns, extension points, and best practices for creating robust content management systems.

Architecture Overview

The CMS Framework follows Laravel's modular architecture patterns:

src/
├── Modules/
│   └── Users/
│       ├── Http/
│       │   ├── Controllers/
│       │   └── Resources/
│       ├── Models/
│       │   └── Concerns/
│       ├── Managers/
│       ├── Providers/
│       └── routes/
├── CMSFrameworkServiceProvider.php
└── ...

Key Components

  • Service Providers: Bootstrap and register framework components
  • Modules: Self-contained feature sets (Users, Content, etc.)
  • Managers: Business logic abstraction layer
  • Models: Eloquent models with relationships and concerns
  • Controllers: API endpoint handlers
  • Resources: API response transformations

Extending the Framework

Creating Custom Modules

Follow the existing Users module structure to create new modules:

// src/Modules/Content/ContentServiceProvider.php
<?php

namespace ArtisanPackUI\CMSFramework\Modules\Content;

use Illuminate\Support\ServiceProvider;

class ContentServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->loadMigrationsFrom(__DIR__ . '/database/migrations');
        $this->loadRoutesFrom(__DIR__ . '/routes/api.php');
    }
    
    public function register()
    {
        // Register services
    }
}

Adding Custom Controllers

Extend the base functionality with your own controllers:

// src/Modules/Content/Http/Controllers/ContentController.php
<?php

namespace ArtisanPackUI\CMSFramework\Modules\Content\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class ContentController extends Controller
{
    public function index()
    {
        // Implementation
    }
}

Custom Models with RBAC

Create models that integrate with the role-permission system:

// src/Modules/Content/Models/Post.php
<?php

namespace ArtisanPackUI\CMSFramework\Modules\Content\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    protected $fillable = [
        'title',
        'content',
        'status',
        'author_id',
    ];
    
    public function author(): BelongsTo
    {
        $userModel = config('cms-framework.user_model');
        return $this->belongsTo($userModel, 'author_id');
    }
    
    // Scope for checking user permissions
    public function scopeVisibleTo($query, $user)
    {
        if ($user->hasPermissionTo('view-all-content')) {
            return $query;
        }
        
        return $query->where('author_id', $user->id);
    }
}

Customization Patterns

Extending User Functionality

Adding User Profile Fields

Create a migration to extend the users table:

// database/migrations/add_profile_fields_to_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddProfileFieldsToUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('phone')->nullable();
            $table->text('bio')->nullable();
            $table->string('avatar')->nullable();
            $table->timestamp('last_login_at')->nullable();
        });
    }
    
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['phone', 'bio', 'avatar', 'last_login_at']);
        });
    }
}

Extending User Model

Add methods to your User model:

// app/Models/User.php
class User extends Authenticatable
{
    use HasRolesAndPermissions;
    
    protected $fillable = [
        'name', 'email', 'password', 'phone', 'bio', 'avatar'
    ];
    
    public function getAvatarUrlAttribute()
    {
        return $this->avatar 
            ? Storage::url($this->avatar)
            : 'https://ui-avatars.com/api/?name=' . urlencode($this->name);
    }
    
    public function updateLastLogin()
    {
        $this->update(['last_login_at' => now()]);
    }
    
    public function isOnline()
    {
        return $this->last_login_at && $this->last_login_at->gt(now()->subMinutes(5));
    }
}

Custom API Controllers

Override default behavior by extending the framework controllers:

// app/Http/Controllers/CustomUserController.php
<?php

namespace App\Http\Controllers;

use ArtisanPackUI\CMSFramework\Modules\Users\Http\Controllers\UserController as BaseUserController;
use Illuminate\Http\Request;

class CustomUserController extends BaseUserController
{
    public function store(Request $request)
    {
        // Add custom validation
        $request->validate([
            'phone' => 'nullable|string|max:20',
            'bio' => 'nullable|string|max:500',
        ]);
        
        // Call parent method
        $response = parent::store($request);
        
        // Add custom logic (e.g., send welcome email)
        $user = $response->resource;
        Mail::to($user->email)->send(new WelcomeEmail($user));
        
        return $response;
    }
    
    public function index()
    {
        // Add custom filtering
        $userModel = config('cms-framework.user_model');
        $users = $userModel::with(['roles'])
            ->when(request('role'), function ($query, $role) {
                $query->whereHas('roles', function ($q) use ($role) {
                    $q->where('slug', $role);
                });
            })
            ->when(request('status'), function ($query, $status) {
                if ($status === 'online') {
                    $query->where('last_login_at', '>', now()->subMinutes(5));
                }
            })
            ->paginate(15);
        
        return UserResource::collection($users);
    }
}

Update your routes to use the custom controller:

// routes/api.php
Route::apiResource('users', CustomUserController::class);

Custom Permissions and Middleware

Dynamic Permission Creation

Create permissions programmatically based on models:

// app/Services/PermissionService.php
<?php

namespace App\Services;

use ArtisanPackUI\CMSFramework\Modules\Users\Models\Permission;

class PermissionService
{
    public function createModelPermissions($modelName)
    {
        $actions = ['view', 'create', 'edit', 'delete'];
        $modelSlug = str($modelName)->kebab()->plural();
        
        foreach ($actions as $action) {
            Permission::firstOrCreate([
                'slug' => "{$action}-{$modelSlug}"
            ], [
                'name' => ucfirst($action) . ' ' . str($modelName)->plural(),
            ]);
        }
    }
}

Advanced Permission Middleware

Create middleware that checks model-specific permissions:

// app/Http/Middleware/CheckModelPermission.php
<?php

namespace App\Http\Middleware;

use Closure;

class CheckModelPermission
{
    public function handle($request, Closure $next, $model, $action = 'view')
    {
        $user = $request->user();
        $modelSlug = str($model)->kebab()->plural();
        $permission = "{$action}-{$modelSlug}";
        
        if (!$user || !$user->hasPermissionTo($permission)) {
            abort(403, "Missing permission: {$permission}");
        }
        
        return $next($request);
    }
}

Use in routes:

Route::middleware(['auth', 'model.permission:post,edit'])->group(function () {
    Route::put('/posts/{post}', [PostController::class, 'update']);
});

Advanced Integration Patterns

Event-Driven Architecture

Listen for user events and trigger actions:

// app/Listeners/UserEventListener.php
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Registered;

class UserEventListener
{
    public function handleLogin(Login $event)
    {
        $event->user->updateLastLogin();
        
        // Log user activity
        activity()
            ->performedOn($event->user)
            ->log('User logged in');
    }
    
    public function handleRegistration(Registered $event)
    {
        // Assign default role
        $defaultRole = Role::where('slug', 'user')->first();
        if ($defaultRole) {
            $event->user->roles()->attach($defaultRole->id);
        }
        
        // Send welcome email
        Mail::to($event->user)->send(new WelcomeEmail($event->user));
    }
}

Register the listener:

// app/Providers/EventServiceProvider.php
protected $listen = [
    Login::class => [
        UserEventListener::class . '@handleLogin',
    ],
    Registered::class => [
        UserEventListener::class . '@handleRegistration',
    ],
];

API Response Customization

Create custom API resources for different contexts:

// app/Http/Resources/DetailedUserResource.php
<?php

namespace App\Http\Resources;

use ArtisanPackUI\CMSFramework\Modules\Users\Http\Resources\UserResource;

class DetailedUserResource extends UserResource
{
    public function toArray($request)
    {
        return array_merge(parent::toArray($request), [
            'profile' => [
                'phone' => $this->phone,
                'bio' => $this->bio,
                'avatar_url' => $this->avatar_url,
                'last_login_at' => $this->last_login_at,
                'is_online' => $this->isOnline(),
            ],
            'permissions' => $this->getAllPermissions(),
            'statistics' => [
                'posts_count' => $this->posts()->count(),
                'comments_count' => $this->comments()->count(),
            ],
        ]);
    }
    
    private function getAllPermissions()
    {
        return $this->roles
            ->flatMap->permissions
            ->unique('id')
            ->pluck('slug');
    }
}

Database Query Optimization

Use eager loading and query optimization:

// app/Http/Controllers/OptimizedUserController.php
public function index()
{
    $userModel = config('cms-framework.user_model');
    
    $users = $userModel::select([
            'id', 'name', 'email', 'created_at', 'last_login_at'
        ])
        ->with([
            'roles:id,name,slug',
            'roles.permissions:id,name,slug'
        ])
        ->withCount(['posts', 'comments'])
        ->when(request('search'), function ($query, $search) {
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                  ->orWhere('email', 'like', "%{$search}%");
            });
        })
        ->latest()
        ->paginate(15);
    
    return DetailedUserResource::collection($users);
}

Cache Integration

Implement caching for expensive operations:

// app/Services/UserPermissionCache.php
<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;

class UserPermissionCache
{
    public function getUserPermissions($user)
    {
        return Cache::tags(['user-permissions', "user-{$user->id}"])
            ->remember("user-{$user->id}-permissions", 3600, function () use ($user) {
                return $user->roles
                    ->flatMap->permissions
                    ->unique('id')
                    ->pluck('slug')
                    ->toArray();
            });
    }
    
    public function clearUserPermissions($user)
    {
        Cache::tags(["user-{$user->id}"])->flush();
    }
    
    public function clearAllUserPermissions()
    {
        Cache::tags(['user-permissions'])->flush();
    }
}

Use in your User model:

// app/Models/User.php
public function hasPermissionTo(string $permission): bool
{
    $userPermissions = app(UserPermissionCache::class)->getUserPermissions($this);
    return in_array($permission, $userPermissions);
}

Testing Extensions

Feature Testing

Test your custom functionality:

// tests/Feature/CustomUserTest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\User;
use ArtisanPackUI\CMSFramework\Modules\Users\Models\Role;

class CustomUserTest extends TestCase
{
    public function test_user_can_be_created_with_profile_data()
    {
        $userData = [
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => 'password123',
            'phone' => '+1234567890',
            'bio' => 'Test bio',
        ];
        
        $response = $this->postJson('/api/v1/users', $userData);
        
        $response->assertStatus(201)
                 ->assertJsonStructure([
                     'data' => [
                         'id', 'name', 'email', 'profile' => [
                             'phone', 'bio', 'avatar_url'
                         ]
                     ]
                 ]);
    }
    
    public function test_user_permissions_are_cached()
    {
        $user = User::factory()->create();
        $role = Role::factory()->create();
        $user->roles()->attach($role->id);
        
        // First call should hit database
        $permissions = app(UserPermissionCache::class)->getUserPermissions($user);
        
        // Second call should use cache
        $cachedPermissions = app(UserPermissionCache::class)->getUserPermissions($user);
        
        $this->assertEquals($permissions, $cachedPermissions);
    }
}

Unit Testing

Test individual components:

// tests/Unit/UserPermissionTest.php
<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\Models\User;
use ArtisanPackUI\CMSFramework\Modules\Users\Models\Role;
use ArtisanPackUI\CMSFramework\Modules\Users\Models\Permission;

class UserPermissionTest extends TestCase
{
    public function test_user_has_permission_through_role()
    {
        $user = User::factory()->create();
        $role = Role::factory()->create();
        $permission = Permission::factory()->create();
        
        $role->permissions()->attach($permission->id);
        $user->roles()->attach($role->id);
        
        $this->assertTrue($user->hasPermissionTo($permission->slug));
    }
    
    public function test_user_without_role_has_no_permissions()
    {
        $user = User::factory()->create();
        $permission = Permission::factory()->create();
        
        $this->assertFalse($user->hasPermissionTo($permission->slug));
    }
}

Performance Optimization

Database Indexing

Add appropriate indexes for common queries:

-- For user role queries
CREATE INDEX idx_role_user_user_id ON role_user(user_id);
CREATE INDEX idx_role_user_role_id ON role_user(role_id);

-- For permission role queries  
CREATE INDEX idx_permission_role_role_id ON permission_role(role_id);
CREATE INDEX idx_permission_role_permission_id ON permission_role(permission_id);

-- For user searches
CREATE INDEX idx_users_name ON users(name);
CREATE INDEX idx_users_email ON users(email);

Query Optimization

Use database views for complex queries:

-- Create a view for user permissions
CREATE VIEW user_permissions AS
SELECT 
    u.id as user_id,
    u.name as user_name,
    u.email as user_email,
    p.slug as permission_slug,
    p.name as permission_name
FROM users u
JOIN role_user ru ON u.id = ru.user_id  
JOIN roles r ON ru.role_id = r.id
JOIN permission_role pr ON r.id = pr.role_id
JOIN permissions p ON pr.permission_id = p.id;

Eager Loading Strategies

Define relationship loading patterns:

// app/Models/User.php
protected $with = ['roles']; // Always load roles

public function scopeWithPermissions($query)
{
    return $query->with(['roles.permissions']);
}

public function scopeWithStats($query)
{
    return $query->withCount(['posts', 'comments', 'logins']);
}

Security Best Practices

Input Sanitization

Always sanitize input data:

// app/Http/Requests/CreateUserRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreateUserRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name' => 'required|string|max:255|regex:/^[\pL\s\-]+$/u',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
            'phone' => 'nullable|string|regex:/^[\+]?[0-9\s\-\(\)]{10,20}$/',
            'bio' => 'nullable|string|max:1000',
        ];
    }
    
    protected function prepareForValidation()
    {
        $this->merge([
            'name' => strip_tags($this->name),
            'bio' => strip_tags($this->bio),
        ]);
    }
}

Rate Limiting

Implement comprehensive rate limiting:

// app/Http/Kernel.php
protected $middlewareGroups = [
    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

protected $routeMiddleware = [
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'throttle.login' => \App\Http\Middleware\LoginThrottleMiddleware::class,
];

Custom throttling for sensitive operations:

// app/Http/Middleware/LoginThrottleMiddleware.php
public function handle($request, Closure $next, $maxAttempts = 5, $decayMinutes = 1)
{
    $key = $this->resolveRequestSignature($request);
    $maxAttempts = (int) $maxAttempts;
    
    if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
        throw $this->buildException($key, $maxAttempts);
    }
    
    $this->limiter->hit($key, $decayMinutes * 60);
    
    return $next($request);
}

Deployment Considerations

Environment Configuration

Create environment-specific configurations:

// config/cms-framework.php
return [
    'user_model' => env('CMS_USER_MODEL', \App\Models\User::class),
    'cache_permissions' => env('CMS_CACHE_PERMISSIONS', true),
    'permission_cache_ttl' => env('CMS_PERMISSION_CACHE_TTL', 3600),
    'api_rate_limit' => env('CMS_API_RATE_LIMIT', 60),
    'enable_soft_deletes' => env('CMS_ENABLE_SOFT_DELETES', false),
];

Monitoring and Logging

Implement comprehensive logging:

// app/Providers/AppServiceProvider.php
public function boot()
{
    // Log all permission checks
    Event::listen('cms.permission.checked', function ($user, $permission, $result) {
        Log::info('Permission checked', [
            'user_id' => $user->id,
            'permission' => $permission,
            'result' => $result,
            'ip' => request()->ip(),
        ]);
    });
}
  • [[Installation Guide]] - Getting started with the framework
  • [[Configuration]] - Framework configuration options
  • [[User Management]] - User management features
  • [[Roles and Permissions]] - RBAC system details
  • [[User API Reference]] - API endpoint documentation

For basic usage, start with the [[Quick Start]] guide.