AdminController Extension Guide
Overview
TheAdminController is a powerful base controller that provides CRUD operations, menu management, and standardized responses for admin interfaces. This guide covers how to create new controllers that extend AdminController.
Quick Start
Basic Controller Structure
Copy
<?php
namespace App\Http\Controllers\YourNamespace;
use App\Http\Controllers\Core\AdminController;
use App\Models\YourNamespace\YourModel;
use Illuminate\Http\Request;
class YourController extends AdminController
{
public function __construct()
{
// Set required properties
$this->modelClass = YourModel::class;
$this->entity = 'YourEntity';
$this->namespace = 'YourNamespace';
// Call parent constructor AFTER setting properties
parent::__construct();
// Configure controller-specific settings
$this->setupController();
}
private function setupController()
{
// Configure Inertia page paths
$this->inertia_page_index = 'YourNamespace/YourEntity/Index';
$this->inertia_page_create = 'YourNamespace/YourEntity/Create';
$this->inertia_page_edit = 'YourNamespace/YourEntity/Edit';
$this->inertia_page_view = 'YourNamespace/YourEntity/View';
// Configure routes
$this->controller_base_route = 'your-namespace.your-entity';
$this->controller_index_route = 'your-namespace.your-entity.index';
// Configure search and sort fields
$this->searchable_fields = ['name', 'email', 'description'];
$this->sortable_fields = ['created_at', 'updated_at', 'name'];
// Configure validation rules
$this->setupValidation();
}
}
Required Properties
Essential Properties (Must Set)
Copy
protected string $modelClass = ''; // Fully qualified model class name
protected string $entity = ''; // Entity name for messages/defaults
protected string $namespace = ''; // Controller namespace for organization
Inertia Page Configuration
Copy
protected string $inertia_page_index = ''; // e.g., 'Directory/Member/Index'
protected string $inertia_page_create = ''; // e.g., 'Directory/Member/Create'
protected string $inertia_page_edit = ''; // e.g., 'Directory/Member/Edit'
protected string $inertia_page_view = ''; // e.g., 'Directory/Member/View'
Route Configuration
Copy
protected string $controller_base_route = ''; // e.g., 'directory.member'
protected string $controller_index_route = ''; // e.g., 'directory.member.index'
Optional Configuration Properties
Search & Filter Configuration
Copy
protected array $searchable_fields = []; // Fields for global search
protected array $sortable_fields = []; // Fields allowed for sorting
protected array $filter_casts = []; // Type casting for filters
Copy
$this->searchable_fields = ['name', 'email', 'phone', 'address'];
$this->sortable_fields = ['created_at', 'updated_at', 'name', 'email'];
$this->filter_casts = [
'status' => 'boolean',
'age' => 'integer',
'salary' => 'float'
];
Validation Rules
Copy
protected array $validator_create = []; // Rules for creating records
protected array $validator_update = []; // Rules for updating records
Copy
private function setupValidation()
{
$this->validator_create = [
'name' => 'required|string|max:255|unique:members,name',
'email' => 'required|email|unique:members,email',
'phone' => 'nullable|string|max:20',
];
$this->validator_update = [
'name' => 'required|string|max:255|unique:members,name,' . $this->request_id,
'email' => 'required|email|unique:members,email,' . $this->request_id,
'phone' => 'nullable|string|max:20',
];
}
Pagination & Display
Copy
protected int $per_page = 15; // Items per page
protected string $data_view_default = 'table'; // Default view type
protected array $extra_props = []; // Additional props for views
Authorization
Copy
protected string $auth_entity = ''; // Authorization entity name
Copy
$this->auth_entity = 'all-members';
Method Overrides
Query Modification
Override this method to add custom query logic:Copy
public function additionalQuery(Request $request, $query, ?string $type = null)
{
if ($type === 'index') {
// Add eager loading for index
$query->with(['category', 'tags']);
// Add custom filters
if ($request->filled('category_id')) {
$query->where('category_id', $request->category_id);
}
}
if ($type === 'view' || $type === 'edit') {
// Add more detailed relationships for single item views
$query->with(['category', 'tags', 'reviews.user']);
}
return $query;
}
Page-Specific Customization
Copy
public function index(Request $request)
{
// Set page-specific data
$this->data_object_extra = [
'totalActive' => $this->model->where('status', 'active')->count(),
'statistics' => $this->calculateStatistics(),
];
// Set breadcrumbs
$this->breadcrumbs = [
['title' => 'Dashboard', 'href' => route('main.dashboard.index')],
['title' => 'Your Section', 'href' => route('your.section.index')],
['title' => 'Your Entity List', 'href' => route($this->controller_index_route)],
];
return parent::index($request);
}
public function create(Request $request)
{
// Add form options
$this->extra_props['categories'] = Category::orderBy('name')->get(['id', 'name']);
$this->extra_props['statuses'] = ['active', 'inactive', 'pending'];
return parent::create($request);
}
Form Options Setup
Copy
public function setupFormOptions(Request $request, $formOptions, $data = null)
{
// Add dynamic form options based on current data or request
$formOptions['categories'] = Category::where('active', true)
->orderBy('name')
->get(['id', 'name']);
if ($data && isset($data['category_id'])) {
$formOptions['subcategories'] = Subcategory::where('category_id', $data['category_id'])
->get(['id', 'name']);
}
return $formOptions;
}
Lifecycle Hooks
Create Hooks
Copy
protected function beforeStore(Request $request, array $validatedData): array
{
// Modify data before storing
$validatedData['user_id'] = auth()->id();
$validatedData['slug'] = Str::slug($validatedData['name']);
return $validatedData;
}
protected function afterStore(Request $request, Model $instance): void
{
// Perform actions after storing
// Log activity
activity('created')
->performedOn($instance)
->withProperties($instance->toArray())
->log('Created new ' . $this->entity);
// Send notifications
// Cache clearing
// etc.
}
Update Hooks
Copy
protected function beforeUpdate(Request $request, array $validatedData, Model $instance): array
{
// Store original data for comparison
$this->original_data = $instance->getOriginal();
// Modify data before updating
if (isset($validatedData['name']) && $validatedData['name'] !== $instance->name) {
$validatedData['slug'] = Str::slug($validatedData['name']);
}
return $validatedData;
}
protected function afterUpdate(Request $request, Model $instance): void
{
// Log changes
$changes = $instance->getChanges();
if (!empty($changes)) {
activity('updated')
->performedOn($instance)
->withProperties([
'old' => $this->original_data,
'new' => $changes
])
->log('Updated ' . $this->entity);
}
}
Delete Hooks
Copy
protected function beforeDestroy(Request $request, Model $instance): void
{
// Check for dependencies
if ($instance->children()->count() > 0) {
throw new \Exception('Cannot delete item with child records');
}
// Backup data
$this->backup_data = $instance->toArray();
}
protected function afterDestroy(Request $request, Model $instance): void
{
// Clean up related data
// Log deletion
activity('deleted')
->withProperties($this->backup_data)
->log('Deleted ' . $this->entity);
}
Menu Configuration
Top Menu Level Control
Copy
public function index(Request $request)
{
// Control which level of menu to show in top navigation
$this->generateTopMenu(1); // Show children of active group (default)
// $this->generateTopMenu(0); // Show main groups instead
return parent::index($request);
}
Advanced Features
Custom Actions
Copy
public function customAction(Request $request, $id)
{
$item = $this->model->findOrFail($id);
// Perform custom logic
$result = $this->performCustomOperation($item);
$message = $result ? 'Operation completed successfully' : 'Operation failed';
$type = $result ? 'success' : 'error';
return redirect()
->route($this->controller_index_route)
->with($type, $message);
}
Modal Support
Copy
public function index(Request $request)
{
// Handle modal requests
if ($request->filled('modal') && $request->filled('item_id')) {
$modalType = $request->input('modal');
$itemId = $request->input('item_id');
switch ($modalType) {
case 'show-details':
$this->extra_props['modalData'] = $this->model
->with(['relations'])
->findOrFail($itemId);
break;
}
}
return parent::index($request);
}
Complete Example
Copy
<?php
namespace App\Http\Controllers\Directory;
use App\Http\Controllers\Core\AdminController;
use App\Models\Directory\Member;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class MemberController extends AdminController
{
public function __construct()
{
// Required properties
$this->modelClass = Member::class;
$this->entity = 'Member';
$this->namespace = 'Directory';
// Must call parent constructor after setting required properties
parent::__construct();
// Configure controller
$this->setupController();
}
private function setupController()
{
// Inertia pages
$this->inertia_page_index = 'Directory/Member/Index';
$this->inertia_page_create = 'Directory/Member/Create';
$this->inertia_page_edit = 'Directory/Member/Edit';
$this->inertia_page_view = 'Directory/Member/View';
// Routes
$this->controller_base_route = 'directory.member';
$this->controller_index_route = 'directory.member.index';
// Authorization
$this->auth_entity = 'all-members';
// Search & sort configuration
$this->searchable_fields = ['name', 'email', 'phone', 'address'];
$this->sortable_fields = ['created_at', 'updated_at', 'name', 'email'];
// Validation
$this->validator_create = [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:members,email',
'phone' => 'nullable|string|max:20',
];
$this->validator_update = [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:members,email,' . $this->request_id,
'phone' => 'nullable|string|max:20',
];
}
public function additionalQuery(Request $request, $query, ?string $type = null)
{
if ($type === 'index') {
$query->with(['category', 'status']);
}
return $query;
}
public function index(Request $request)
{
$this->data_object_extra = [
'totalMembers' => Member::count(),
'activeMembers' => Member::where('status', 'active')->count(),
];
$this->breadcrumbs = [
['title' => 'Dashboard', 'href' => route('main.dashboard.index')],
['title' => 'Directory', 'href' => route('directory.dashboard.index')],
['title' => 'Member List', 'href' => route($this->controller_index_route)],
];
return parent::index($request);
}
}
Best Practices
- Always call
parent::__construct()after setting required properties - Set breadcrumbs in each page method for better navigation
- Use lifecycle hooks for complex business logic
- Configure search and sort fields for better user experience
- Add proper validation rules for data integrity
- Use consistent naming conventions for routes and pages
- Handle authorization through the auth_entity property
- Add meaningful extra_props for enhanced UI functionality
