Running your first strict-type PHP script
Concept
Writing your first PHP script under strict types is not just "Hello World" — it is establishing the habits that separate professional PHP code from the kind you find on StackOverflow answers from 2009. declare(strict_types=1) must be the first statement in every file you write. Understanding why is more important than the syntax itself.
The declare(strict_types=1) statement
In PHP's default mode (non-strict), the engine performs type coercion on function arguments. Pass an integer 42 to a function expecting string, and PHP silently converts it to "42". This seems convenient but creates bugs: passing "0" to a function expecting bool coerces to false, which is correct but may be surprising. Passing "" to a function expecting int returns 0. These implicit conversions hide programming mistakes.
With strict_types=1:
- The declaration must be the very first statement (before namespace, before
<?phptag content) - It applies only to the file it's declared in, not to files it includes
- Function calls with wrong types throw a
TypeErrorinstead of silently coercing - Built-in PHP functions remain loosely typed (this affects only user-defined functions called from this file)
<?php
declare(strict_types=1); // Must be first
// Now this throws TypeError if $age is not an int:
function greet(string $name, int $age): string {
return "Hello {$name}, you are {$age} years old.";
}
greet("Alice", 30); // Works
greet("Bob", "25"); // TypeError: must be int, string givenFile structure conventions
A PSR-12 compliant PHP file follows this exact order:
- Opening
<?phptag (no closing?>tag in pure PHP files — omitting it prevents accidental whitespace injection) declare(strict_types=1);- File-level docblock (optional)
namespacedeclarationuseimport statements- Class/function/constant definitions
Executing scripts
From the command line: php script.php. PHP will output anything echoed or printed to stdout.
Exit codes matter: exit(0) = success, exit(1) = generic error. Shell scripts and CI systems check these.
Code Example
<?php
declare(strict_types=1);
/**
* A complete, properly structured PHP 8.4 script.
* Demonstrates strict types, named arguments, match, and modern syntax.
*/
namespace App\Learning;
// Value object — immutable, equality by value
final class Temperature
{
public function __construct(
public readonly float $celsius,
) {
if ($this->celsius < -273.15) {
throw new \ValueError(
"Temperature {$this->celsius}°C is below absolute zero"
);
}
}
public function toFahrenheit(): float
{
return $this->celsius * 9 / 5 + 32;
}
public function toKelvin(): float
{
return $this->celsius + 273.15;
}
public function describe(): string
{
return match(true) {
$this->celsius < 0 => 'freezing',
$this->celsius < 15 => 'cold',
$this->celsius < 25 => 'comfortable',
$this->celsius < 35 => 'warm',
default => 'hot',
};
}
public function __toString(): string
{
return sprintf(
"%.1f°C / %.1f°F / %.2fK (%s)",
$this->celsius,
$this->toFahrenheit(),
$this->toKelvin(),
$this->describe(),
);
}
}
// Main execution
$temperatures = [
new Temperature(celsius: -10.0),
new Temperature(celsius: 20.0),
new Temperature(celsius: 37.0),
new Temperature(celsius: 100.0),
];
foreach ($temperatures as $temp) {
echo $temp . "\n";
}
// Using array_find() — PHP 8.4
$bodyTemp = array_find(
$temperatures,
fn(Temperature $t): bool => $t->celsius === 37.0
);
if ($bodyTemp !== null) {
echo "\nBody temperature found: {$bodyTemp}\n";
}
// Exit with success code — important for scripts used in CI
exit(0);Interview Q&A
Q: Why does declare(strict_types=1) only affect the file it's declared in, not files it includes?
PHP's strict type mode is a per-file compile-time directive, not a runtime global. When PHP compiles file-a.php with strict_types=1, it marks the compiled opcodes for that file to enforce strict argument checking when calling functions from file-a.php. If file-a.php includes file-b.php which has no strict declaration, calls made in file-b.php still use loose mode. This design was intentional: it lets you gradually add strict types to a codebase file by file without breaking existing loosely-typed code. The implication is that strict types only apply at the call site — if a function is defined in a strict file but called from a loose file, the loose rules apply for that call.
Q: What is the significance of omitting the closing ?> tag in PHP files?
The closing ?> tag is optional for pure-PHP files (files with no HTML after the PHP code). Omitting it prevents a subtle but serious bug: any whitespace, newline, or UTF-8 BOM after the closing tag gets sent as output. When this happens before header() calls, session starts, or HTTP redirects, you get the dreaded "headers already sent" error. By convention, PSR-12 requires omitting the closing tag in pure-PHP files. The only time you need the closing tag is in mixed PHP/HTML templates, and even then, modern practice using template engines (Blade, Twig) makes direct PHP in HTML files rare.
Q: Why do TypeErrors from strict mode help you catch bugs earlier than loose coercion?
Type coercion silently masks the real bug — if you pass a string where an integer is expected and PHP silently converts it, the function runs with potentially corrupted data. The error surfaces later, far from the original cause, as a wrong calculation result or a confusing database value. A TypeError thrown immediately at the call site tells you exactly which argument, in which function, with which value violated the contract. This dramatically reduces debugging time. It also makes your code's intent explicit: a function signature sum(int $a, int $b) with strict types is a machine-enforced contract, not a suggestion. This is especially important in team environments where you cannot trust that callers read the docblock.