π Building the Admin Dashboard
π Project Structure
This structure outlines how the admin dashboard interface integrates with the rest of the PHP MVC application. New and updated files are noted for clarity.
project-root/
βββ admin/
β βββ dashboard.php β π entry point for admin dashboard
βββ config/
β βββ init.php β handles session start and access control helpers
βββ controllers/
β βββ UserController.php β handles login, registration, and admin actions
βββ models/
β βββ db_connect.php β PDO database connection
β βββ UserModel.php β logic for user CRUD operations and role checks
βββ views/
β βββ admin/
β β βββ dashboard.php β π UI for admin to view/manage users
β βββ partials/
β β βββ header.php β navigation UI with role-based links
β β βββ footer.php β shared layout footer
β βββ profile/
β βββ delete-verify.php β π confirmation view for deleting accounts
β βββ ... β other user profile views
βββ deactivate.php β handles soft delete via is_blocked flag
βββ delete.php β π POST entry point for hard deletes
βββ login.php β processes login form using UserController
βββ register.php β handles user registration
βββ index.php β home or landing page
π§ Setting a Project Root Path
As your app grows and adds folders like admin/ and partials/, keeping track of relative file paths becomes tricky. Instead of writing long or fragile ../.. chains, we can define a reusable base path that always points to the root of your project β no matter where you are in the folder tree.
Step 1: Add to config/init.php
At the top of your init.php, define a constant that stores the root folder path:
<?php
session_start();
date_default_timezone_set('America/Chicago');
// Define the absolute path to the project root
define('BASE_PATH', dirname(__DIR__));
__DIR__- This is a built-in PHP constant that returns the full folder path of the current file. In this case,
config/. dirname(__DIR__)- This goes one level up to find the root folder of your app, no matter where the current file is located.
define('BASE_PATH', ...)- We save this value as a global constant named
BASE_PATHso any file can use it when including other files.
Step 2: Use It in Controllers or Entry Points
Now, instead of doing this:
require_once '../../models/UserModel.php';
You can do this:
require_once BASE_PATH . '/models/UserModel.php';
This approach is cleaner, safer, and portable across different environments (like localhost vs. live servers).
Why Now?
As we add deeper folders like views/admin/ or controllers/admin/, using BASE_PATH helps keep your app organized and your include paths error-free.
π‘ Tip: Stick to using BASE_PATH only for includes/require statements on the file system. For web links (URLs), youβll use a different approach covered later.
π Admin Dashboard Entry Point
This file is the entry point for accessing the admin dashboard. Like other front-controller pages in the app (e.g., profile.php), it initializes the session, checks for proper authorization, and delegates all processing to the appropriate controller method.
By checking the $_SESSION['role'], it ensures only users with the admin role can reach the dashboard. Unauthorized visitors are redirected with a friendly error message.
The call to UserController::dashboard() allows all database queries and view rendering to stay inside the controller β keeping the route itself clean and focused.
<?php
// admin/dashboard.php
require_once BASE_PATH . '/config/init.php';
require_once BASE_PATH . '/controllers/UserController.php';
if ($_SESSION['role'] !== 'admin') {
header("Location: " . BASE_PATH . "/index.php?error=" . urlencode("Access denied."));
exit;
}
UserController::dashboard();
π€ Controller: Handling Logic
The UserController::dashboard() method serves as the central logic handler for the admin dashboard. It does the following:
- Loads the
UserModelto fetch all user records - Stores the results in a variable
$users - Loads the dashboard view and passes the data along
By keeping model calls and view rendering in the controller, the app remains organized and scalable.
// in UserController.php
public static function dashboard()
{
require_once BASE_PATH . '/models/UserModel.php';
$users = UserModel::getAllUsers();
require BASE_PATH . '/views/admin/dashboard.php';
}
π₯ View: Display Only UI
This view receives the $users array from the controller and displays it in a table. It performs no database queries or session checks β just renders the HTML UI for the admin dashboard.
<?php include BASE_PATH . '/views/partials/header.php'; ?>
<h2>Admin Dashboard</h2>
<table>
<thead>
<tr><th>ID</th><th>Username</th><th>Role</th><th>Status</th><th>Actions</th></tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= htmlspecialchars($user['id']) ?></td>
<td><?= htmlspecialchars($user['username']) ?></td>
<td><?= htmlspecialchars($user['role']) ?></td>
<td><?= $user['is_blocked'] ? 'Blocked' : 'Active' ?></td>
<td>
<a href="<?= BASE_PATH ?>/views/profile/edit.php?id=<?= urlencode($user['id']) ?>">Edit</a>
<a href="<?= BASE_PATH ?>/views/profile/deactivate.php?id=<?= urlencode($user['id']) ?>">Deactivate</a>
<a href="<?= BASE_PATH ?>/delete.php?id=<?= urlencode($user['id']) ?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php include BASE_PATH . '/views/partials/footer.php'; ?>
This pattern keeps display concerns separate from logic and data handling, which aligns with MVC best practices.
π Security Notes
- Always include
init.phpat the start of route entry points for shared setup. - Redirect unauthorized users via query string instead of echoing βAccess deniedβ.
- Use
htmlspecialchars()andurlencode()for output safety. - Keep controllers responsible for flow logic and views for presentationβstick to MVC.
ποΈ Delete Users
Delete Entry Point
Create a new delete.php file in the project root. It connects to the controller and deletes the user:
<?php
require_once BASE_PATH . '/config/init.php';
require_once BASE_PATH . '/controllers/UserController.php';
UserController::delete();
Controller: Confirm or Execute Delete
The UserController::delete() method handles both GET and POST. It shows a confirmation page if accessed via GET, and deletes the user if submitted via POST:
public static function delete()
{
require_once BASE_PATH . '/models/UserModel.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
if ($id && UserModel::deleteUser($id)) {
header("Location: admin/dashboard.php?success=" . urlencode("User deleted."));
exit;
}
header("Location: admin/dashboard.php?error=" . urlencode("Failed to delete user."));
exit;
}
$id = $_GET['id'] ?? null;
if ($id) {
require BASE_PATH . '/views/profile/delete-verify.php';
return;
}
header("Location: admin/dashboard.php?error=" . urlencode("No user ID specified."));
exit;
}
Model: Delete from Database
Add a method to UserModel to permanently remove a user by ID:
public static function deleteUser($id)
{
$db = getDB();
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
return $stmt->execute([$id]);
}
Delete Confirmation View
Create views/profile/delete-verify.php to confirm the deletion before executing:
<form method="post" action="../../delete.php">
<input type="hidden" name="id" value="<?= htmlspecialchars($_GET['id']) ?>">
<p>Are you sure you want to delete this account? This action cannot be undone.</p>
<button type="submit" name="delete">Confirm Delete</button>
<a href="../admin/dashboard.php">Cancel</a>
</form>
π Security Tips
- Always check roles in both views and controllers β never rely on UI hiding alone.
- Use POST for all state-changing actions (block, delete, promote).
- Escape all user output with
htmlspecialchars()before rendering.
Next Steps
This interface can be extended with search, pagination, user filters, and activity logs. But for now, it provides a complete working admin toolset for managing accounts securely within a PHP MVC architecture.
Last updated: August 8, 2025 at 8:57 PM