PHP Syntax, Types, and Data Handling
A surprising number of production bugs come down to fundamentals: a string that was secretly a number, a float used for money, a date in the wrong time zone. Modern PHP gives you real tools to say what you mean, and it's still an easy language to pick up.
On this page
Start new files with strict types
<?php
declare(strict_types=1);
function calculateTax(float $subtotal, float $rate): float
{
return round($subtotal * $rate, 2);
}
echo calculateTax(125.00, 0.06);
Strict mode stops PHP from quietly converting types when you call a function, like turning "5 apples" into 5. It won't validate user input for you, but it makes your own code a lot more predictable. I put it at the top of every new file.
Common types
| Type | Use | Example |
|---|---|---|
string | Text and identifiers that are not calculated | $invoiceNumber = 'INV-2026-0042'; |
int | Whole numbers | $seatCount = 28; |
float | Approximate decimal values | $distanceMiles = 17.4; |
bool | True/false state | $isActive = true; |
array | Ordered maps and lists | $roles = ['admin', 'dispatcher']; |
object | State plus behavior | $customer = new Customer(...); |
0.1 + 0.2 is not 0.3 in floating point, and eventually someone's invoice will be off by a penny. Store integer cents, or use a decimal/money library.Nullable and union types
<?php
declare(strict_types=1);
function normalizePhone(string|null $phone): ?string
{
if ($phone === null || trim($phone) === '') {
return null;
}
return preg_replace('/\D+/', '', $phone) ?: null;
}
function displayId(int|string $id): string
{
return (string) $id;
}
Use nullable types when "no value" actually means something. Don't reach for a big union type just to avoid deciding what a function should accept. That's usually a sign the function is doing too much.
Strings and where they end up
<?php
declare(strict_types=1);
$name = trim($_POST['name'] ?? '');
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo "<p>Welcome, {$safeName}.</p>";
Escape when you output, not when you take input, because the right escaping depends on where the string is going. HTML text, an HTML attribute, JSON, SQL, a shell command, and a URL all have different rules.
Dates and time zones
<?php
declare(strict_types=1);
$displayZone = new DateTimeZone('America/New_York');
$storedUtc = new DateTimeImmutable('2026-07-16 13:30:00', new DateTimeZone('UTC'));
$local = $storedUtc->setTimezone($displayZone);
echo $local->format('F j, Y g:i A T');
My rule: store timestamps in UTC, keep the time zone name (like America/New_York) alongside anything scheduled in local time, and only convert when showing it to a person. Daylight saving time will find every shortcut you take.
Clean up input once, at the edge
Turn raw request data into predictable values once, as soon as it comes in. Trim strings, validate IDs, turn checkboxes into booleans, parse dates explicitly, and reject anything ambiguous. Then the rest of your code can trust what it's working with.
<?php
declare(strict_types=1);
$rawQuantity = $_POST['quantity'] ?? null;
$quantity = filter_var(
$rawQuantity,
FILTER_VALIDATE_INT,
['options' => ['min_range' => 1, 'max_range' => 100]]
);
if ($quantity === false) {
throw new InvalidArgumentException('Quantity must be between 1 and 100.');
}
