Skip to content

Lab 5: Implementing READ and CREATE Operations

In this lab, you will implement two core CRUD operations in your admin panel: READ (listing products) and CREATE (adding new products).

  • In Part I, you will build a product listing page that displays all products from the database in a table.
  • In Part II, you will implement the CREATE operation, which involves displaying a creation form (GET request) and handling the form submission (POST request).
    • You will apply the Post-Redirect-Get (PRG) pattern to prevent duplicate submissions and use the FlashMessage helper (from Lab 4) to provide user feedback.

By completing this lab, you will:

  • Build a product listing page that queries and displays data from the database.
  • Implement the CREATE operation with GET (display form) and POST (handle submission) routes.
  • Build controller methods that handle form data extraction and validation.
  • Create model methods to fetch records and insert new ones.
  • Build an HTML form that submits data to the correct POST route.
  • Apply the Post-Redirect-Get (PRG) pattern to prevent duplicate submissions.
  • Integrate FlashMessage for success and error feedback after form submission.

Before starting this lab, ensure you have:

  • Completed Lab 2 (admin panel with route group, admin layout, and ViewHelper).
  • Completed Lab 4 (FlashMessage helper class).
  • The products table in your database with sample data.

In this part, you will create the ProductsModel and ProductsController classes, then build a page that lists all products from the database in the admin panel.

Step 1: Enable Flash Messages in the Admin Layout

Section titled “Step 1: Enable Flash Messages in the Admin Layout”

Objective: Uncomment the FlashMessage rendering in the admin header so flash messages display on all admin pages.

Instructions:

  1. Open app/Views/admin/adminHeader.php
  2. Find the commented-out flash message block:
<!-- Flash Messages (uncomment when FlashMessage helper is available) -->
<!-- <div class="mb-3">
<?= App\Helpers\FlashMessage::render() ?>
</div> -->
  1. Uncomment the block so it looks like this:
<!-- Flash Messages -->
<div class="mb-3">
<?= App\Helpers\FlashMessage::render() ?>
</div>
  1. Save the file

Objective: Create a model class for querying the products table.

Instructions:

  1. Navigate to your app/Domain/Models/ directory
  2. Create a new file named ProductsModel.php
  3. Copy and paste the following class skeleton:
app/Domain/Models/ProductsModel.php
<?php
namespace App\Domain\Models;
use App\Helpers\Core\PDOService;
class ProductsModel extends BaseModel
{
public function __construct(PDOService $pdoService)
{
parent::__construct($pdoService);
}
/**
* Fetch all products from the database.
*/
public function getAll(): array
{
// TODO: Execute a SELECT query to fetch all products
// - Select: id, name, price, stock_quantity, created_at
// - Order by: created_at DESC (newest first)
// - Use $this->fetchAll() to return all rows as an array
}
/**
* Fetch all categories for the product form dropdown.
*/
public function getAllCategories(): array
{
// TODO: Execute a SELECT query to fetch all categories (id, name)
// - Order by: name ASC
// - Use $this->fetchAll() to return all rows as an array
}
}
  1. Implement the TODO in the getAll() method
  2. Save the file

Objective: Create a controller class that uses the ProductsModel to handle product-related requests.

Instructions:

  1. Navigate to your app/Controllers/ directory
  2. Create a new file named ProductsController.php
  3. Copy and paste the following class skeleton:
app/Controllers/ProductsController.php
<?php
namespace App\Controllers;
use App\Domain\Models\ProductsModel;
use DI\Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class ProductsController extends BaseController
{
public function __construct(
Container $container,
private ProductsModel $productsModel
) {
parent::__construct($container);
}
/**
* Display the list of all products.
*/
public function index(Request $request, Response $response, array $args): Response
{
// TODO: 1. Fetch all products using $this->productsModel->getAll()
// TODO: 2. Create a $data array with 'title' and 'products' keys
// TODO: 3. Render the view 'admin/products/products.IndexView.php' and pass $data
}
}
  1. Implement the TODO comments in the index() method
  2. Save the file

Objective: Add a route to display the product listing page.

Instructions:

  1. Open app/Routes/web-routes.php
  2. Add the following import at the top:
use App\Controllers\ProductsController;
  1. Inside the existing $app->group('/admin', ...) block, add a GET route for the product list:
app/Routes/web-routes.php
$app->group('/admin', function ($group) {
// ... existing dashboard route ...
// TODO: Add GET route for /products
// - Controller: ProductsController::class, 'index'
// - Named route: 'products.index'
});
  1. Save the file

Objective: Build a view that displays all products in an HTML table.

Instructions:

  1. Navigate to app/Views/admin/products/ (create the products/ folder if it does not exist)
  2. Create a new file named products.IndexView.php
  3. Set up the view structure:
app/Views/admin/products/products.IndexView.php
<?php
use App\Helpers\ViewHelper;
ViewHelper::loadAdminHeader($title);
?>
<!-- TODO: Add an "Add Product" button/link that points to the create form -->
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<tr>
<!-- TODO: Display each product's id, name, price, stock_quantity, and created_at -->
<!-- Hint: Use hs() for text fields and number_format() for the price -->
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php ViewHelper::loadAdminFooter(); ?>
  1. Save the file

Objective: Verify that the product listing page displays correctly.

Instructions:

  1. Start your development server (if not already running)
  2. Visit http://localhost/[your-app-name]/admin/products in your browser
  3. Verify that:
    • The page displays all products from the database in a table
    • Product names, prices, stock quantities, and creation dates are shown correctly
    • The admin layout (sidebar, header) wraps the content

In this part, you will add the ability to create new products from the admin panel.

Objective: Add two routes for the CREATE operation inside the existing /admin route group.

Instructions:

  1. Open app/Routes/web-routes.php
  2. Inside the /admin group (below the product list route from Step 4), add two routes:
app/Routes/web-routes.php
$app->group('/admin', function ($group) {
// ... existing dashboard and products index routes ...
// TODO: Add GET route for /products/create
// - Controller: ProductsController::class, 'create'
// - Named route: 'products.create'
// TODO: Add POST route for /products
// - Controller: ProductsController::class, 'store'
});
  1. Save the file

Objective: Add create() and store() methods to ProductsController with FlashMessage integration.

Instructions:

  1. Open app/Controllers/ProductsController.php
  2. Add the following import at the top:
use App\Helpers\FlashMessage;

Method 1: create() - Display the Creation Form

Section titled “Method 1: create() - Display the Creation Form”
  1. Add the create() method that renders the product creation form:
app/Controllers/ProductsController.php
public function create(Request $request, Response $response, array $args): Response
{
// TODO: 1. Fetch all categories using $this->productsModel->getAllCategories()
// TODO: 2. Create a $data array with 'title', and 'categories' keys
// TODO: 3. Render the view 'admin/products/products.CreateView.php' and pass $data
}

Method 2: store() - Handle Form Submission

Section titled “Method 2: store() - Handle Form Submission”
  1. Add the store() method that processes the submitted form data:
app/Controllers/ProductsController.php
public function store(Request $request, Response $response, array $args): Response
{
// TODO: 1. Get form data using $request->getParsedBody()
// TODO: 2. Validate required fields (name and price)
// - If validation fails, set an error flash message and redirect back to 'products.create'
// TODO: 3. Save to database using $this->productsModel->createAndGetId()
// TODO: 4. Set a success flash message
// TODO: 5. Redirect to the product list ('products.index')
}
  1. Save the file

Objective: Add a method to ProductsModel that inserts a new product and returns its ID.

Instructions:

  1. Open app/Domain/Models/ProductsModel.php
  2. Add the following method:
app/Domain/Models/ProductsModel.php
public function createAndGetId(array $data): string
{
// TODO: 1. Execute an INSERT query using $this->execute()
// - Insert: category_id, name, price, description, created_at
// - Use named parameters (:category_id, :name, :price, :description, :created_at)
// TODO: 2. Return the last inserted ID using $this->lastInsertId()
}
  1. Save the file

Step 10: Create the Product Creation Form View

Section titled “Step 10: Create the Product Creation Form View”

Objective: Build the HTML form that submits product data to the POST route.

Instructions:

  1. Navigate to app/Views/admin/products/
  2. Create a new file named products.CreateView.php
  3. Set up the view structure:
app/Views/admin/products/products.CreateView.php
<?php
use App\Helpers\ViewHelper;
ViewHelper::loadAdminHeader($title);
?>
<form method="POST" action="<?= APP_BASE_URL ?>/admin/products">
<!-- TODO: Add a select dropdown for category (required) -->
<!-- Use ViewHelper::renderSelectOptions($categories, '', 'id', 'name') -->
<!-- to generate the <option> elements -->
<!-- TODO: Add input field for product name (text, required) -->
<!-- TODO: Add input field for price (number, step="0.01", required) -->
<!-- TODO: Add textarea for description (optional) -->
<!-- TODO: Add submit button -->
<!-- TODO: Add cancel link back to the admin product list -->
</form>
<?php ViewHelper::loadAdminFooter(); ?>
  1. Save the file

Objective: Verify that the CREATE operation works correctly with flash messages.

Instructions:

  1. Visit http://localhost/[your-app-name]/admin/products/create in your browser
  2. Test the following scenarios:
    • Submit the form with empty fields: you should see an error flash message and be redirected back to the form
    • Fill in valid product data and submit: you should see a success flash message and be redirected to the product list
    • Check your database to confirm the new product was inserted correctly
    • Verify the new product appears in the product listing page from Part I
    • Refresh the page after a successful creation: no duplicate product should be inserted (PRG pattern working)