πŸ“– 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_PATH so 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 UserModel to 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.php at the start of route entry points for shared setup.
  • Redirect unauthorized users via query string instead of echoing β€œAccess denied”.
  • Use htmlspecialchars() and urlencode() 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