Lab 2: Building an Admin Panel
Additional Resources
Section titled “Additional Resources”- Slim 4 Route Groups ↗
- Working with the BaseModel class
- Working with the BaseController class
- Database Tables
Overview
Section titled “Overview”In this lab, you will build the foundation of an admin panel for your e-commerce application. You will create a dashboard controller and model that query real statistics from your database, set up a route group to organize all admin routes under the /admin prefix, and build a reusable admin layout with sidebar navigation. By the end of this lab, you will have a professional-looking admin dashboard ready for CRUD operations in future labs.
Learning Objectives
Section titled “Learning Objectives”By completing this lab, you will:
- Use Slim 4 route groups to organize related routes under a common prefix.
- Build a dashboard controller that queries aggregate statistics from the database.
- Use the BaseModel’s
count()method to retrieve summary data. - Create a reusable admin layout with header, sidebar navigation, and footer.
- Understand the data flow from controller to view using the
$dataarray. - Apply
htmlspecialchars()for secure output in views.
Prerequisites
Section titled “Prerequisites”Before starting this lab, ensure you have:
- A working Slim 4 application with the MVC structure.
- The BaseModel and BaseController classes set up.
- Bootstrap CSS included in your views.
Part I: Create the Database Tables
Section titled “Part I: Create the Database Tables”In this part, you will create the categories and products tables in your database. These tables are needed for the admin dashboard to query statistics from.
Step 1: Create the Categories Table
Section titled “Step 1: Create the Categories Table”Objective: Create the categories table to organize products into logical groups.
Instructions:
- Open phpMyAdmin and select your e-commerce database
- Go to the SQL tab and write a
CREATE TABLEstatement for thecategoriestable with the following columns:
| Column | Type | Constraints | Description |
|---|---|---|---|
id | INT | PRIMARY KEY, AUTO_INCREMENT | Category ID |
name | VARCHAR(100) | UNIQUE, NOT NULL | Category name |
description | TEXT | NULL | Category description |
created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Creation timestamp |
- Run the query and verify the table was created by checking the left sidebar in phpMyAdmin
Step 2: Create the Products Table
Section titled “Step 2: Create the Products Table”Objective: Create the products table to store product information.
Instructions:
- In phpMyAdmin, go to the SQL tab and write a
CREATE TABLEstatement for theproductstable with the following columns:
| Column | Type | Constraints | Description |
|---|---|---|---|
id | INT | PRIMARY KEY, AUTO_INCREMENT | Product ID |
category_id | INT | FOREIGN KEY → categories(id) | Category this product belongs to |
name | VARCHAR(100) | NOT NULL | Product name |
description | TEXT | NULL | Product details |
price | DECIMAL(10,2) | NOT NULL | Product price |
stock_quantity | INT | DEFAULT 0 | Units available |
created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Creation date |
updated_at | DATETIME | ON UPDATE CURRENT_TIMESTAMP | Last update date |
- Run the query and verify the table was created and that the foreign key relationship to
categoriesis in place
Step 3: Insert Sample Data
Section titled “Step 3: Insert Sample Data”Objective: Add some sample data so the dashboard has something to display.
Instructions:
- Insert a few categories:
INSERT INTO categories (name, description) VALUES('Electronics', 'Electronic devices and accessories'),('Books', 'Physical and digital books'),('Clothing', 'Apparel and fashion items');- Insert a few products:
INSERT INTO products (category_id, name, description, price, stock_quantity) VALUES(1, 'Wireless Mouse', 'Ergonomic wireless mouse', 29.99, 50),(1, 'USB-C Hub', '7-in-1 USB-C adapter', 49.99, 3),(2, 'PHP in Action', 'A comprehensive guide to PHP', 39.99, 15),(3, 'Developer T-Shirt', '100% cotton, black', 24.99, 0);Part II: Build the Admin Panel
Section titled “Part II: Build the Admin Panel”Step 4: Create the AdminDashboardModel
Section titled “Step 4: Create the AdminDashboardModel”Objective: Create a model that queries aggregate statistics from the database for the admin dashboard.
Instructions:
-
Navigate to your
app/Domain/Models/directory -
Create a new file named
AdminDashboardModel.php -
Set up the class:
- Use the namespace
App\Domain\Models - Import
App\Helpers\Core\PDOService - Create a class
AdminDashboardModelthat extendsBaseModel - Add a constructor that accepts a
PDOServiceparameter and passes it to the parent constructor
- Use the namespace
-
Implement the following two methods in the class:
-
getTotalProducts(): int: Returns the total number of products in the database. Use$this->count()with aSELECT COUNT(*)query on theproductstable. -
getTotalCategories(): int: Returns the total number of categories. Follow the same pattern asgetTotalProducts()but query thecategoriestable.
-
-
Save the file
Hints:
- Refer to the BaseModel documentation for method signatures and examples
Step 5: Create the AdminDashboardController
Section titled “Step 5: Create the AdminDashboardController”Objective: Create a controller that uses the AdminDashboardModel to fetch statistics and render the dashboard view.
Instructions:
- Navigate to your
app/Controllers/directory - Create a new file named
AdminDashboardController.php - Copy and paste the following code:
<?php
namespace App\Controllers;
use App\Domain\Models\AdminDashboardModel;use DI\Container;use Psr\Http\Message\ResponseInterface as Response;use Psr\Http\Message\ServerRequestInterface as Request;
class AdminDashboardController extends BaseController{ public function __construct( Container $container, private AdminDashboardModel $dashboardModel ) { parent::__construct($container); }
/** * Display the admin dashboard with e-commerce statistics. */ public function dashboard(Request $request, Response $response, array $args): Response { // TODO: Call the model methods to get dashboard statistics // - Total products → $this->dashboardModel->getTotalProducts() // - Total categories → $this->dashboardModel->getTotalCategories()
// TODO: Build a $data array with the following keys: // - 'title' => 'Admin Dashboard' // - 'totalProducts' => (from model) // - 'totalCategories' => (from model) // - 'username' => $_SESSION['username'] ?? 'Admin'
// TODO: Render the view 'admin/dashboardView.php' and pass the $data array }}- Implement the TODO comments in the
dashboard()method - Save the file
Hints:
- Use
$_SESSION['username'] ?? 'Admin'to safely get the username (falls back to ‘Admin’ if not set) - Refer to the BaseController documentation for the render pattern
Step 6: Set Up the Admin Route Group
Section titled “Step 6: Set Up the Admin Route Group”Objective: Create a route group for all admin routes under the /admin prefix.
Instructions:
- Open your
app/Routes/web-routes.phpfile - Add the following import at the top of the file:
use App\Controllers\AdminDashboardController;- Add the admin route group:
// Admin Panel Routes$app->group('/admin', function ($group) { // Dashboard route $group->get('/dashboard', [AdminDashboardController::class, 'dashboard']) ->setName('admin.dashboard');
// TODO (future labs): Add CRUD routes for products, categories, orders, users // $group->get('/products', [ProductsController::class, 'index']) // ->setName('admin.products.index'); // $group->get('/categories', [CategoriesController::class, 'index']) // ->setName('admin.categories.index'); // $group->get('/orders', [OrdersController::class, 'index']) // ->setName('admin.orders.index'); // $group->get('/users', [UsersController::class, 'index']) // ->setName('admin.users.index');});// Note: Later you'll add ->add(AdminAuthMiddleware::class) to protect these routes- Save the file
Routes Created:
| Method | URI | Controller Method | Route Name |
|---|---|---|---|
| GET | /admin/dashboard | AdminDashboardController::dashboard() | admin.dashboard |
Step 7: Create the Admin Layout (Header + Footer)
Section titled “Step 7: Create the Admin Layout (Header + Footer)”Objective: Create a reusable admin layout with a sidebar navigation and top bar that can be shared across all admin views.
Instructions:
- Navigate to your
app/Views/directory - Create a new folder named
admin/(if it doesn’t already exist)
Step 7.1: Create the Admin Header
Section titled “Step 7.1: Create the Admin Header”- Inside
app/Views/admin/, create a file namedadminHeader.php - Copy and paste the following code:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= htmlspecialchars($title ?? 'Admin Panel') ?></title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous"> <style> .admin-sidebar { min-height: 100vh; background-color: #212529; } .admin-sidebar .nav-link { color: #adb5bd; padding: 0.75rem 1rem; border-radius: 0.25rem; margin-bottom: 0.25rem; } .admin-sidebar .nav-link:hover, .admin-sidebar .nav-link.active { color: #fff; background-color: #495057; } .admin-content { background-color: #f8f9fa; min-height: 100vh; } .stat-card { transition: transform 0.2s; } .stat-card:hover { transform: translateY(-2px); } </style></head><body> <div class="container-fluid"> <div class="row"> <!-- Sidebar --> <nav class="col-md-3 col-lg-2 d-md-block admin-sidebar p-0"> <div class="p-3"> <h5 class="text-white mb-0">Admin Panel</h5> <small class="text-muted">E-Commerce Manager</small> </div> <hr class="text-secondary my-0"> <ul class="nav flex-column px-2 mt-2"> <li class="nav-item"> <a class="nav-link active" href="<?= APP_BASE_URL ?>/admin/dashboard"> Dashboard </a> </li> <li class="nav-item"> <a class="nav-link" href="<?= APP_BASE_URL ?>/admin/products"> Products </a> </li> <li class="nav-item"> <a class="nav-link" href="<?= APP_BASE_URL ?>/admin/categories"> Categories </a> </li> <li class="nav-item"> <a class="nav-link" href="<?= APP_BASE_URL ?>/admin/orders"> Orders </a> </li> <li class="nav-item"> <a class="nav-link" href="<?= APP_BASE_URL ?>/admin/users"> Users </a> </li> </ul> <hr class="text-secondary"> <div class="px-2"> <a class="nav-link text-muted" href="<?= APP_BASE_URL ?>/"> ← Back to Store </a> </div> </nav>
<!-- Main Content --> <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 admin-content"> <!-- Top bar --> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="h2"><?= htmlspecialchars($title ?? 'Dashboard') ?></h1> <div class="text-muted"> Logged in as: <strong><?= htmlspecialchars($username ?? 'Admin') ?></strong> </div> </div>
<!-- Flash Messages (uncomment when FlashMessage helper is available) --> <!-- <div class="mb-3"> <?= App\Helpers\FlashMessage::render() ?> </div> -->
<!-- Page content starts here -->- Save the file
Step 7.2: Create the Admin Footer
Section titled “Step 7.2: Create the Admin Footer”- Inside
app/Views/admin/, create a file namedadminFooter.php - Copy and paste the following code:
<!-- Page content ends here --> </main> </div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script></body></html>- Save the file
What you just created:
- adminHeader.php: A reusable layout with a dark sidebar (navigation links to all admin sections), a top bar showing the page title and logged-in username, and a flash messages area.
- adminFooter.php: Closes the HTML structure and includes Bootstrap JavaScript.
- Together, they wrap any admin page content.
Step 7.3: Add Admin Methods to ViewHelper
Section titled “Step 7.3: Add Admin Methods to ViewHelper”- Open
app/Helpers/ViewHelper.php - Add the following two methods to the class:
public static function loadAdminHeader(string $page_title): void{ $page_title = $page_title ?? 'Admin Panel'; require_once APP_VIEWS_PATH . '/admin/adminHeader.php';}
public static function loadAdminFooter(): void{ require_once APP_VIEWS_PATH . '/admin/adminFooter.php';}- Save the file
Step 8: Create the Dashboard View
Section titled “Step 8: Create the Dashboard View”Objective: Create the dashboard view that displays e-commerce statistics using the data passed from the controller.
Instructions:
- Inside
app/Views/admin/, create a file nameddashboardView.php - Copy and paste the following code:
<?php
use App\Helpers\ViewHelper;
ViewHelper::loadAdminHeader($title);?>
<!-- Statistics Cards --><div class="row mb-4"> <div class="col-xl-3 col-md-6 mb-3"> <div class="card stat-card border-primary"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-muted mb-1">Total Products</h6> <h3 class="mb-0"><?= htmlspecialchars($totalProducts) ?></h3> </div> <div class="text-primary fs-1">📦</div> </div> </div> </div> </div> <div class="col-xl-3 col-md-6 mb-3"> <div class="card stat-card border-warning"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-muted mb-1">Categories</h6> <h3 class="mb-0"><?= htmlspecialchars($totalCategories) ?></h3> </div> <div class="text-warning fs-1">📁</div> </div> </div> </div> </div></div>
<?php ViewHelper::loadAdminFooter(); ?>- Save the file
What you just created:
- A dashboard view that includes the reusable admin header and footer
- Two stat cards showing totals for products and categories
Key PHP features used in this view:
| Feature | Example | Purpose |
|---|---|---|
ViewHelper | ViewHelper::loadAdminHeader($title) | Reusable layout pattern |
htmlspecialchars() | htmlspecialchars($totalProducts) | Prevent XSS attacks |
Understanding What You Built
Section titled “Understanding What You Built”Architecture Overview
Section titled “Architecture Overview”Request: GET /admin/dashboard ↓Route Group (/admin) ↓AdminDashboardController::dashboard() ↓AdminDashboardModel (queries database statistics) ↓dashboardView.php (renders HTML with stats) ↓Response sent to browser