Welcome to the fourth and last part of these series of tutorial on how to make a simple CRUD with PHP using an example of a simple products management application. As announced in part 3 today we're going to see how we can READ
, UPDATE
, and DELETE
data with PHP and MYSQL. And the previous part we saw how we could create new entries.
- READ
Reading consists of us fetching existing data from our database. In our case we already have a link which is going to be use to access the list of products, and it links to the page list-products.php
.
In order to display the list of our products inn this page we will first start from this:
<?php
// Start from getting the hader which contains some settings we need
require_once 'includes/header.php';
// Redirect visitor to the login page if he is trying to access
// this page without being logged in
if (!isset($_SESSION['admin_session']) )
{
$commons->redirectTo(SITE_PATH.'index.php');
}
// require the admins class which containes most functions applied to admins
require_once ROOT."../includes/classes/admin-class.php";
// This could be a counstant
$numberOfProductsToFetch = 25;
$admins = new Admins($dbh);
$products = $admins->fetchProducts($numberOfProductsToFetch);
?>
Notice the $products
variable. It contains the list of the first 25 products. Now all we have to do is to call a PHP loop to list them as following:
<h3>List Products</h3>
<?php if (isset($products) && sizeof($products) > 0) :?>
<?php foreach ($products as $product) :?>
<li><a href="view-product-details.php?id=<?= $product->id ?>" title="Click to view product"><?= htmlspecialchars(strip_tags($product->product_name)) ?></a></li>
<?php endforeach ?>
<?php else: ?>
<h3>No product is added yet.</h3>
<?php endif ?>
In this last code you can notice we are calling another page: view-product-details.php
. We pass the product ID to this page via the global $_GET
. Once this page has the id it can fetch the specific product with that ID. and display its details. The code in view-product-details.php
looks like:
<?php
// Start from getting the hader which contains some settings we need
require_once 'includes/header.php';
// Redirect visitor to the login page if he is trying to access
// this page without being logged in
if (!isset($_SESSION['admin_session']) )
{
$commons->redirectTo(SITE_PATH.'index.php');
}
// Get the product ID
$productId = isset($_GET['id']) && intval($_GET['id']) > 0 ? intval($_GET['id']) : 0;
if ($productId > 0) {
// require the admins class which containes most functions applied to admins
require_once ROOT."../includes/classes/admin-class.php";
$admins = new Admins($dbh);
$productDetails = $admins->getAProduct($productId);
}
?>
<?php if (isset($productDetails) && sizeof($productDetails) > 0) : $product = $productDetails[0]; ?>
<!-- We will use a simple table to display the product -->
<h1><?= htmlentities(strip_tags($product->product_name)) ?></h1>
<hr>
<table width="100%" border="0">
<tr>
<td>Price</td>
<td>: <strong><?= htmlentities(strip_tags($product->product_price)) ?></strong> </td>
</tr>
<tr>
<td>Expiry date</td>
<td>: <strong><?= htmlentities(strip_tags($product->product_expires_on)) ?></strong> </td>
</tr>
<tr>
<td colspan="2">
<br>
<?= htmlentities(strip_tags(nl2br($product->product_description))) ?>
</td>
</tr>
</table>
<br>
<hr>
<br>
<ul class="btns">
<li><a href="edit-product.php?id=<?= $product->id ?>" class="btn-1a">Edit</a></li>
<li><a href="delete-product.php?id=<?= $product->id ?>" class="btn-1a" onclick="return confirm('Are you sure you want to delete this item?');">Delete</a></li>
</ul>
<?php else: ?>
<h3>No product is select.</h3>
<?php endif ?>
- UPDATE
The update consists of fetching a specific item, display it in a form then resubmit the form with that data. So, in order to update, we need to READ
the product's details, then display it in a form. Once that form is submitted, we can modify the product's details in our products
table based on its ID.
The page in charge of editing producted is name edit-product.php
. We call it everytime with a product ID for it to know whch product we want to edit.
The source code in edit-product.php
is:
<?php
// Start from getting the hader which contains some settings we need
require_once 'includes/header.php';
// Redirect visitor to the login page if he is trying to access
// this page without being logged in
if (!isset($_SESSION['admin_session']) )
{
$commons->redirectTo(SITE_PATH.'index.php');
}
// Get the product ID
$productId = isset($_GET['id']) && intval($_GET['id']) > 0 ? intval($_GET['id']) : 0;
if ($productId > 0) {
// require the admins class which containes most functions applied to admins
require_once ROOT."../includes/classes/admin-class.php";
$admins = new Admins($dbh);
$productDetails = $admins->getAProduct($productId);
}
?>
<?php if (isset($productDetails) && sizeof($productDetails) > 0) : $product = $productDetails[0]; ?>
<!-- We will use a simple table to display the product -->
<h1>Edit: <?= htmlentities(strip_tags($product->product_name)) ?></h1>
<hr>
<?php if ( isset($_SESSION['errors']) ): ?>
<div class="pannel panel-warning">
<?php foreach ($_SESSION['errors'] as $error):?>
<li><?= $error ?></li>
<?php endforeach ?>
</div>
<?php session::destroy('errors'); endif ?>
<?php if ( isset($_SESSION['confirm']) ): ?>
<div class="pannel panel-success">
<li><?= $_SESSION['confirm'] ?></li>
</div>
<?php session::destroy('confirm'); endif ?>
<!-- We send the form information to process-new-admin.php to handle it -->
<form action="process-edited-product.php" method="POST">
<div>
<label for="name">Product Name</label>
<input type="text" name="name" id="name" value="<?= isset($product->product_name) ? htmlspecialchars(strip_tags($product->product_name)) : '' ?>">
<input type="hidden" name="id" value="<?= isset($product->id) ? htmlspecialchars(strip_tags($product->id)) : '' ?>">
</div>
<div>
<label for="price">Price(Unit)</label>
<input type="number" name="price" id="price" min="1" step="any" value="<?= isset($product->product_price) ? htmlspecialchars(strip_tags($product->product_price)) : '' ?>">
</div>
<div>
<label for="expiry">Expires on</label>
<input type="date" name="expiry" id="expiry" value="<?= isset($product->product_expires_on) ? htmlspecialchars(strip_tags($product->product_expires_on)) : '' ?>">
</div>
<div>
<label for="description">Description</label>
<textarea name="description" id="description" cols="30" rows="10"><?= isset($product->product_description) ? htmlspecialchars(strip_tags($product->product_description)) : '' ?></textarea>
</div>
<div class="activate">
<button type="submit" class="btn-1a">Save changes</button>
</div>
</form>
- DELETE
Deleting is one of the easiest things to do in this concept. There are many ways of implementating it. One thing you should always pay attention to is that this may be used just to delete an item, but in case someone comes to understand how he could access such function, he can simply delete all your items. You have to protect this option very well. In my case I made it simple, starting from a link pointing the page in charge of deleting an item. I also pass the item ID to the page which is now in charge of calling the deletion function. But before doing that we check if the person accessing it is a connected admin. Once that is done, we are automatically redirected to the main list of products.
The link to delete a product:
<a href="delete-product.php?id=<?= $product->id ?>" class="btn-1a" onclick="return confirm('Are you sure you want to delete this item?');">Delete</a>
The Javascript onClick()
function is used to warn the user on the action he's taking.
**Then in delete-product.php
we have:
<?php
// Start from getting the hader which contains some settings we need
require_once 'includes/header.php';
// Redirect visitor to the login page if he is trying to access
// this page without being logged in
// in here this condition will help protect our products from attackers
if (!isset($_SESSION['admin_session']) )
{
$commons->redirectTo(SITE_PATH.'index.php');
}
// Get the product ID
$productId = isset($_GET['id']) && intval($_GET['id']) > 0 ? intval($_GET['id']) : 0;
if ($productId > 0)
{
// require the admins class which containes most functions applied to admins
require_once ROOT."../includes/classes/admin-class.php";
$admins = new Admins($dbh);
// Call the delete method
$productDetails = $admins->deleteProduct($productId);
// redirect to the list of items
$commons->redirectTo(SITE_PATH.'list-products.php');
}
Already you can notice that those actions we are executing are defined in the includes/classes/admin-class.php
. This class has also been modified and looks like this now:
<?php
/**
* The admins class
* It contains all action and behaviors admins may have
*/
class Admins
{
private $dbh = null;
public function __construct($db)
{
$this->dbh = $db->dbh;
}
public function loginAdmin($username, $password)
{
//Un-comment this to see a cryptogram of a password
// echo session::hashPassword($password);
// die;
$request = $this->dbh->prepare("SELECT username, password FROM admins WHERE username = ?");
if($request->execute( array($username) ))
{
// This is an array of objects.
// Remember we setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); in config/dbconnection.php
$data = $request->fetchAll();
// But if things are right, the array should contain only one object, the corresponding user
// so, we can do this
$data = $data[0];
return session::passwordMatch($password, $data->password) ? true : false;
}else{
return false;
}
}
/**
* Check if the admin username is unique
* If though we've set this criteria in our database,
* It's good to make sure the user is not try that
* @param $username The username
* @return Boolean If the username is already usedor not
*
*/
public function adminExists( $username )
{
$request = $this->dbh->prepare("SELECT username FROM admins WHERE username = ?");
$request->execute([$username]);
$Admindata = $request->fetchAll();
return sizeof($Admindata) != 0;
}
/**
* Compare two passwords
* @param String $password1, $password2 The two passwords
* @return Boolean Either true or false
*/
public function ArePasswordsSame( $password1, $password2 )
{
return strcmp( $password1, $password2 ) == 0;
}
/**
* Create a new row of admin
* @param String $username New admin username
* @param String $password New Admin password
* @return Boolean The final state of the action
*
*/
public function addNewAdmin($username, $password)
{
$request = $this->dbh->prepare("INSERT INTO admins (username, password) VALUES(?,?) ");
// Do not forget to encrypt the pasword before saving
return $request->execute([$username, session::hashPassword($password)]);
}
/**
* Create a new row of product
*
*/
public function addNewProduct($name, $price, $expiry, $description)
{
$request = $this->dbh->prepare("INSERT INTO products (product_name, product_price, product_expires_on, product_description, created_on) VALUES(?,?,?,?,?) ");
// Do not forget to encrypt the pasword before saving
return $request->execute([$name, $price, $expiry, $description, time()]);
}
/**
* Edit a product
*/
public function updateProduct($id, $name, $price, $expiry, $description)
{
$request = $this->dbh->prepare("UPDATE products SET product_name = ?, product_price = ?, product_expires_on = ?, product_description = ? WHERE id = ? ");
// Do not forget to encrypt the pasword before saving
return $request->execute([$name, $price, $expiry, $description, $id]);
}
/**
* Fetch products
*/
public function fetchProducts($limit = 100)
{
$request = $this->dbh->prepare("SELECT * FROM products ORDER BY product_name LIMIT $limit");
if ($request->execute()) {
return $request->fetchAll();
}
return false;
}
/**
* Fetch one product
*/
public function getAProduct($id)
{
if (is_int($id))
{
$request = $this->dbh->prepare("SELECT * FROM products WHERE id = ?");
if ($request->execute([$id])) {
return $request->fetchAll();
}
return false;
}
return false;
}
/**
* Delete a product
*/
public function deleteProduct($id)
{
$request = $this->dbh->prepare("DELETE FROM products WHERE id = ?");
return $request->execute([$id]);
}
}
- Conclusion
I made this last part very short because I have uploaded my own source code for you to look at it and compare with yours. It will also help you have an overall view of the whole project. I am also going to upload it on my GitHub repository so that from time to time I can update it.
Thank you for your time. If you loved this, leave a comment and share.
Last updated 2024-01-11 UTC