declare(strict_types=1)
Here's a basket that takes an int quantity. Someone posts the form with quantity=1e3. PHP looks at the parameter type, sees a numeric string, and helpfully converts it. They've just ordered a thousand mugs.
Without strict_types, scalar type declarations are suggestions. PHP coerces whatever you pass into the declared type whenever it can:
| You pass | int parameter receives |
|---|---|
"2" |
2 |
"1e3" |
1000 |
"2.5" |
2, with a deprecation notice |
" 2" |
2 |
true |
1 |
"two" |
TypeError |
Only the last one fails. Everything else carries on with a value nobody asked for.
What strict_types changes
declare(strict_types=1); at the top of a file switches off that coercion for calls made from that file. Pass a string to an int parameter and you get a TypeError straight away, at the line that made the mistake.
That makes the type declaration a real contract. It also forces a decision PHP was making for you: when a string arrives from a form, a query string, or a CSV, something has to turn it into an int. With strict types on, that something is your code, in one place, at the boundary. That's where you can say what counts as valid.
Converting at the boundary
(int) $value isn't validation. (int) "1e3" is 1000 and (int) "two" is 0. Use something that can say no:
$quantity = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); if ($quantity === false) { throw new InvalidArgumentException('quantity must be a whole number of at least 1'); }
FILTER_VALIDATE_INT accepts plain whole numbers, with surrounding whitespace. It returns false for 1e3, 2.5, two, and anything below min_range. It also rejects leading zeros, so 02 is false too. That's usually what you want, but worth knowing before a customer finds it.
Power-up
<?php final class RecordStockCount { /** @param array<string, string> $form */ public function handle(array $form): void { // "1e3" quietly becomes 1000 mugs on the shelf. $this->stock->record($form['sku'], $form['count']); } }
<?php declare(strict_types=1); final class RecordStockCount { /** @param array<string, string> $form */ public function handle(array $form): void { $count = filter_var($form['count'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]); if ($count === false) { throw new InvalidArgumentException('count must be a whole number of 0 or more'); } $this->stock->record($form['sku'], $count); } }