Skip to content

PHP Tutorial Practical PHP Projects

Project 1: Simple PHP Calculator

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

Level: beginner. You will build a calculator page: two numbers, an operation, a result. It practises forms, functions, classes, match and error handling.

What it does

  • Takes two numbers and an operation: add, subtract, multiply, divide, power, remainder.
  • Validates the input (real numbers only).
  • Shows a friendly message for division by zero.
  • Keeps the values in the form after you press Calculate.

Step 1: the calculator class

Put the logic in a class that knows nothing about HTML. That makes it easy to test.

Calculator.php
PHP
<?php

declare(strict_types=1);

final class Calculator
{
    public const OPERATIONS = [
        "add" => "+",
        "subtract" => "-",
        "multiply" => "x",
        "divide" => "/",
        "power" => "^",
        "modulo" => "%",
    ];

    public function calculate(string $operation, float $a, float $b): float
    {
        return match ($operation) {
            "add" => $a + $b,
            "subtract" => $a - $b,
            "multiply" => $a * $b,
            "divide" => $b === 0.0 ? throw new DivisionByZeroError("You cannot divide by zero.") : $a / $b,
            "power" => $a ** $b,
            "modulo" => $b === 0.0 ? throw new DivisionByZeroError("You cannot take a remainder by zero.") : fmod($a, $b),
            default => throw new InvalidArgumentException("Unknown operation."),
        };
    }
}

Step 2: try it from the command line

test.php
PHP
<?php
require __DIR__ . "/Calculator.php";

$calc = new Calculator();

echo $calc->calculate("add", 2, 3) . "\n";
echo $calc->calculate("divide", 10, 4) . "\n";
echo $calc->calculate("power", 2, 10) . "\n";
echo $calc->calculate("modulo", 7.5, 2) . "\n";

try {
    $calc->calculate("divide", 5, 0);
} catch (DivisionByZeroError $e) {
    echo $e->getMessage() . "\n";
}

try {
    $calc->calculate("sqrt", 5, 0);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}
Output
OUTPUT
5
2.5
1024
1.5
You cannot divide by zero.
Unknown operation.

Step 3: read and validate the form

functions.php
PHP
<?php

declare(strict_types=1);

function h(string $text): string
{
    return htmlspecialchars($text, ENT_QUOTES, "UTF-8");
}

/** @return array{values: array, errors: array} */
function readCalculatorInput(array $post, array $operations): array
{
    $errors = [];

    $a = trim((string) ($post["a"] ?? ""));
    $b = trim((string) ($post["b"] ?? ""));
    $operation = (string) ($post["operation"] ?? "");

    if (filter_var($a, FILTER_VALIDATE_FLOAT) === false) {
        $errors["a"] = "Enter a number for the first value.";
    }

    if (filter_var($b, FILTER_VALIDATE_FLOAT) === false) {
        $errors["b"] = "Enter a number for the second value.";
    }

    if (!array_key_exists($operation, $operations)) {
        $errors["operation"] = "Choose an operation.";
    }

    return ["values" => ["a" => $a, "b" => $b, "operation" => $operation], "errors" => $errors];
}

Let us check the validation with different input:

index.php
PHP
<?php
require __DIR__ . "/Calculator.php";
require __DIR__ . "/functions.php";

$good = readCalculatorInput(["a" => "6", "b" => "3", "operation" => "divide"], Calculator::OPERATIONS);
$bad = readCalculatorInput(["a" => "six", "b" => "", "operation" => "hack"], Calculator::OPERATIONS);

echo count($good["errors"]) . " errors for good input\n";
echo implode(" | ", $bad["errors"]);
Output
OUTPUT
0 errors for good input
Enter a number for the first value. | Enter a number for the second value. | Choose an operation.

Step 4: the page

index.php
PHP
<?php
declare(strict_types=1);

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

$calculator = new Calculator();
$values = ["a" => "", "b" => "", "operation" => "add"];
$errors = [];
$result = null;
$message = null;

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    ["values" => $values, "errors" => $errors] = readCalculatorInput($_POST, Calculator::OPERATIONS);

    if ($errors === []) {
        try {
            $result = $calculator->calculate($values["operation"], (float) $values["a"], (float) $values["b"]);
        } catch (DivisionByZeroError $e) {
            $message = $e->getMessage();
        }
    }
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Calculator</title>
</head>
<body>
  <h1>Calculator</h1>

  <form method="post" novalidate>
    <p>
      <label for="a">First number</label><br>
      <input id="a" name="a" inputmode="decimal" value="<?= h($values["a"]) ?>">
      <?php if (isset($errors["a"])): ?><br><span role="alert"><?= h($errors["a"]) ?></span><?php endif; ?>
    </p>

    <p>
      <label for="operation">Operation</label><br>
      <select id="operation" name="operation">
        <?php foreach (Calculator::OPERATIONS as $key => $symbol): ?>
          <option value="<?= h($key) ?>" <?= $values["operation"] === $key ? "selected" : "" ?>><?= h($key . " (" . $symbol . ")") ?></option>
        <?php endforeach; ?>
      </select>
      <?php if (isset($errors["operation"])): ?><br><span role="alert"><?= h($errors["operation"]) ?></span><?php endif; ?>
    </p>

    <p>
      <label for="b">Second number</label><br>
      <input id="b" name="b" inputmode="decimal" value="<?= h($values["b"]) ?>">
      <?php if (isset($errors["b"])): ?><br><span role="alert"><?= h($errors["b"]) ?></span><?php endif; ?>
    </p>

    <button type="submit">Calculate</button>
  </form>

  <?php if ($result !== null): ?>
    <p role="status">Result: <strong><?= h((string) round($result, 10)) ?></strong></p>
  <?php endif; ?>

  <?php if ($message !== null): ?>
    <p role="alert"><?= h($message) ?></p>
  <?php endif; ?>
</body>
</html>

Save the four files in one folder and run php -S localhost:8000. Open http://localhost:8000/index.php.

What you practised

SkillWhere
Classes and constantsCalculator, OPERATIONS
match and throw as an expressioncalculate()
ExceptionsDivisionByZeroError, InvalidArgumentException
ValidationreadCalculatorInput()
Escapingh() on every printed value
Forms that keep their valuesvalue="<?= h(...) ?>"

Challenges

  1. Add a history: keep the last five calculations in $_SESSION and show them below the form.
  2. Add square root (one number). How does the form change when an operation needs only one value?
  3. Support chaining: use the previous result as the first number.
  4. Round the display to a number of decimals the user chooses (validate it as an integer from 0 to 10).

Exercise: spot the floating-point surprise

What does $calc->calculate("subtract", 0.3, 0.1) return, and why does the page use round($result, 10) when printing?

Show the answer

It returns 0.19999999999999998, because 0.3 and 0.1 cannot be stored exactly in binary (see Floating-Point Pitfalls). Rounding to 10 decimals for display shows 0.2, while the stored value stays as accurate as a float can be.