Skip to content

PHP Tutorial Database CRUD

Update Records

Lesson 273 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

Update also has two parts: show a form filled with the current values, then save the changes.

The repository method

index.php
PHP
<?php
final class ProductRepository
{
    public function __construct(private PDO $pdo) {}

    public function find(int $id): ?array
    {
        $statement = $this->pdo->prepare("SELECT id, name, price, stock, category FROM products WHERE id = :id");
        $statement->execute(["id" => $id]);

        return $statement->fetch() ?: null;
    }

    public function update(int $id, string $name, float $price, int $stock, string $category): void
    {
        $statement = $this->pdo->prepare(
            "UPDATE products SET name = :name, price = :price, stock = :stock, category = :category WHERE id = :id"
        );
        $statement->execute([
            "name" => $name,
            "price" => $price,
            "stock" => $stock,
            "category" => $category,
            "id" => $id,
        ]);
    }
}

$repo = new ProductRepository(require __DIR__ . "/db.php");

$repo->update(2, "Gel Pen", 15, 90, "writing");

$product = $repo->find(2);

echo $product["name"] . " / stock " . $product["stock"];
Output
OUTPUT
Gel Pen / stock 90

Do not rely on rowCount()

In MySQL, an update that sets the same values changes zero rows, so rowCount() can be 0 even when the product exists. That is why the page checks that the product exists before it updates. Use find() first.

The page: product-edit.php

product-edit.php
PHP
<?php
declare(strict_types=1);

session_start();

require __DIR__ . "/ProductRepository.php";
require __DIR__ . "/functions.php";

$repo = new ProductRepository(require __DIR__ . "/db.php");

$id = filter_var($_GET["id"] ?? $_POST["id"] ?? null, FILTER_VALIDATE_INT);
$product = $id === false ? null : $repo->find($id);

if ($product === null) {
    http_response_code(404);
    exit("Product not found.");
}

$old = $product;           // start with the saved values
$errors = [];

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    if (!hash_equals($_SESSION["csrf_token"] ?? "", $_POST["csrf_token"] ?? "")) {
        http_response_code(403);
        exit("Invalid form token.");
    }

    $old = array_map(fn($v) => is_string($v) ? trim($v) : "", array_intersect_key($_POST, array_flip(["name", "price", "stock", "category"])));
    $errors = validateProduct($old);

    if ($errors === []) {
        $repo->update($id, $old["name"], (float) $old["price"], (int) $old["stock"], $old["category"]);

        $_SESSION["flash"] = "Product updated.";
        header("Location: product.php?id=" . $id);
        exit;
    }
}

$_SESSION["csrf_token"] ??= bin2hex(random_bytes(32));
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Edit product</title></head>
<body>
  <h1>Edit product</h1>

  <form method="post" novalidate>
    <input type="hidden" name="id" value="<?= (int) $id ?>">
    <input type="hidden" name="csrf_token" value="<?= h($_SESSION["csrf_token"]) ?>">

    <p>
      <label for="name">Name</label><br>
      <input id="name" name="name" value="<?= h((string) $old["name"]) ?>" required>
      <?php if (isset($errors["name"])): ?><br><span role="alert"><?= h($errors["name"]) ?></span><?php endif; ?>
    </p>
    <!-- price, stock and category fields work the same way as in the create form -->

    <button type="submit">Save changes</button>
  </form>
</body>
</html>

The flow

  1. Read the id (from the URL on first load, from a hidden field on submit).
  2. Load the product. If it does not exist, answer 404.
  3. GET: show the form filled with the saved values.
  4. POST: verify the token, validate, update, redirect.

Differences from Create

  • The form starts with the saved values ($old = $product).
  • There is a hidden id, and the update has a WHERE id = :id.
  • The 404 check protects against ids that do not exist.

Only allow editing what is yours

If rows belong to users, add the owner to the condition so one user cannot edit another’s data.

index.php
PHP
<?php
$statement = $pdo->prepare("UPDATE posts SET title = :title WHERE id = :id AND user_id = :user");

And load the row with the same condition, so someone else’s id looks like “not found”.

Reuse the form

The create and edit forms are almost identical. Move the form into one file (product-form.php) that both pages include, with $old, $errors and the page title as variables. One form means one place to fix. See Avoiding Duplicate Code.

warning
Trusting the hidden id
The hidden id field can be changed by the visitor. That is fine as long as the server checks that the user may edit that product. Never treat a hidden field as proof of anything.