PHP 8 strict types catch type mismatches at call time, not at assignment time. In a webhook handler, this means a TypeError can crash a request after all the meaningful work is done — leaving the user without a response and a 500 in the logs. This article covers how it happens and how to prevent it.
The Single Missing Argument
declare(strict_types=1);
// A function deep in the call stack
function store_message(int $conversationId, string $role, string $content, int $userId): void
{
// ...
}
// Webhook handler calls a service...
// Service calls a helper...
// Helper calls store_message() with:
store_message($convId, 'assistant', $responseText); // Missing $userId!
// PHP 8: Fatal error: Too few arguments to function store_message()
// This crashes the entire request AFTER the LLM has already generated the response
Without strict_types=1, PHP would accept and silently handle type coercions. With it, any type mismatch is fatal. This is the correct behavior — but it requires catching mismatches before production.
Static Analysis: Finding Type Mismatches Before Deployment
# Install PHPStan (level 8 = most strict)
composer require --dev phpstan/phpstan
# phpstan.neon
parameters:
level: 8
paths:
- src/
# Run before every deployment
./vendor/bin/phpstan analyse
# PHPStan finds: Parameter #4 $userId of function store_message() expects int, missing.
# PSalm is an alternative with different strengths
composer require --dev vimeo/psalm
./vendor/bin/psalm
PHP 8.x Union Types and Nullable Parameters
declare(strict_types=1);
// PHP 8 union types — accept int or string
function process(int|string $id): string
{
return (string) $id;
}
// Nullable shorthand — ?int means int|null
function find_user(?int $id): ?array
{
if ($id === null) return null;
// ...
}
// Named arguments help catch missing args at call site (PHP 8.0+)
store_message(
conversationId: $convId,
role: 'assistant',
content: $responseText,
userId: $userId, // Named: compiler catches this omission
);
What to Watch For
- Webhook context — arguments from payload parsing — Payload fields from
json_decode()are mixed types. Always validate and cast before passing to typed functions:(int) ($payload['id'] ?? 0). - Legacy code without declare(strict_types=1) — Mixing strict and non-strict files in one project is dangerous. The strictness applies per-file. If a non-strict file calls a strict file's function, coercions may still occur.
- PHPStan level progression — Start at level 5 on existing codebases. Level 8 on a large legacy codebase will produce hundreds of errors. Progress level by level.