<? phpukraine СПІВБЕСІДИ
Пошук по платформі
CORE PHP · MIDDLE ЧАСТО ПИТАЮТЬ

Що робить declare(strict_types=1) і як PHP приводить типи в аргументах?

`declare(strict_types=1)` — це директива рівня одного файлу, яка вимикає приведення скалярів для викликів, зроблених із цього файлу: без неї PHP у coercive-режимі намагається сконвертувати аргумент до оголошеного типу (з PHP 8.0 нечислові рядки — це `TypeError`, а числові з «хвостом» — `int` плюс `E_WARNING`), зі strict приймається лише точний тип, з єдиним винятком `int` → `float`.

Чому `strlen(42)` в одному файлі працює, а в сусідньому кидає TypeError?
Я поставив `declare(strict_types=1)` у `bootstrap.php` — чому це не вплинуло на решту файлів?
Функція оголошена з `int $id`, а в базу пішло `0` замість `'abc'` — як так вийшло?
Чому `declare(strict_types=1)` не завадив передати `'10'` у функцію з `int` — файл-виклик чи файл-оголошення тут головний?
strict_types declare TypeError coercion PHP 8.0 type juggling return types

declare(strict_types=1) — це не налаштування рантайму й не ini-опція, а директива часу компіляції з областю дії рівно в один файл. Вона зʼявилась у PHP 7.0 разом зі скалярними тайп-хінтами (RFC Andrea Faulds) і перемикає перевірку оголошених типів між двома режимами: coercive (за замовчуванням) і strict. Записувати її треба першою інструкцією файлу — після <?php не може бути ані пробільного виводу, ані namespace, ані use; аргумент — тільки літерал 0 або 1, змінна там дасть помилку компіляції. Директива не успадковується: include строгого файлу з нестрогого нічого не змінює, автозавантажений клас живе за режимом свого файлу, і саме тому найпоширеніша помилка — поставити рядок в один bootstrap.php і вважати проєкт строгим.

Найважливіша механіка: режим бере той файл, у якому знаходиться виклик, а не той, де функція оголошена. PHP на етапі компіляції позначає опкод виклику прапорцем поточного файлу, і перевірка аргументів у момент виконання йде за цим прапорцем. Практичний наслідок — ваш строгий код, викликаючи бібліотеку без директиви або взагалі внутрішню функцію ядра, отримає строгу перевірку: strlen(42) зі strict-файлу кине TypeError, хоча strlen() до вашого файлу не має жодного стосунку. І навпаки: якщо строга бібліотека викликає ваш нестрогий callback, перевірка буде строгою, бо виклик стався в її файлі.

У coercive-режимі PHP намагається сконвертувати аргумент до оголошеного типу, і з PHP 8.0 ці правила стали значно чеснішими. Повністю числовий рядок ('5', ' 5 ', '1e3') конвертується тихо. Leading-numeric рядок ('5 apples') конвертується у 5, але з E_WARNING. Повністю нечисловий рядок ('apples') з PHP 8.0 — це вже TypeError, тоді як у 7.x він мовчки ставав 0 і саме через це в базу потрапляли записи з id = 0. Окремо PHP 8.1 задеприкейтив неявне floatint із втратою дробової частини: 8.5 у параметр int дає deprecation-повідомлення. У strict-режимі всього цього немає взагалі — приймається лише точний тип, з єдиним винятком: int можна передати в параметр float (widening), бо в PHP немає окремого літерала для «цілого числа з плаваючою комою» і вимагати 100.0 замість 100 було б знущанням. Зворотний напрямок, floatint, заборонений завжди.

Дуже поширене перебільшення на співбесіді — сказати, що strict_types «вимикає type juggling». Не вимикає. Директива діє лише там, де тип оголошено явно: параметри, тип повернення, типізовані властивості (PHP 7.4+) і типізовані константи класів (PHP 8.3+). Усе інше поводиться як завжди: '5' + 5 дає 10, if ($string) приводить до bool, == порівнює за своїми правилами (які, до речі, самі змінились у PHP 8.0 у RFC «Saner string to number comparisons»), ключ масиву '5' збігається з 5, а array_sum() підсумує змішаний масив без питань. Не чіпає директива й нетипізовані параметри — там просто нема чого перевіряти. Тому strict_types корисно розуміти вузько: це заборона неявного приведення скалярів у типізованих сигнатурах, не більше.

Практично це означає дві речі. Перша — вмикати директиву треба автоматично в кожному файлі: правило declare_strict_types у Pint/PHP-CS-Fixer, шаблон файлу в IDE, перевірка в CI; ручна дисципліна тут не працює, а частково строгий проєкт дає найгірший варіант — поведінка залежить від того, з якого файлу прийшов виклик. Друга — межа з зовнішнім світом. HTTP-параметри, значення з CLI, дані з БД у деяких драйверах, JSON без JSON_BIGINT-нюансів — усе це приходить рядками, і в строгому коді конвертація має бути явним, видимим кроком: filter_var(..., FILTER_VALIDATE_INT), FormRequest з правилом integer, $request->integer('page'). Строгі типи не роблять валідацію за вас — вони гарантують, що після точки конвертації неправильний тип уже не протече глибше в домен. І варто памʼятати про верхній шар: PHPStan рівня 6+ ловить ті самі невідповідності статично, до запуску, тоді як strict_types спрацьовує вже в рантаймі — ці два інструменти доповнюють один одного, а не замінюють.

<?php

declare(strict_types=1);          // ПЕРША інструкція файлу, інакше fatal error

final class Cart
{
    public int $itemCount = 0;    // типізована властивість теж під strict

    public function addItem(int $productId, float $price): float
    {
        $this->itemCount++;

        return $price * $productId;
    }
}

$cart = new Cart();

// int → float: ЄДИНЕ послаблення, дозволене і в strict-режимі (widening)
$cart->addItem(7, 100);           // 100 стає 100.0, помилки немає

try {
    // рядок у параметр int: у strict-режимі приведення заборонене
    $cart->addItem('7', 100.0);   // TypeError: Argument #1 must be of type int, string given
} catch (TypeError $e) {
    echo $e->getMessage();
}

try {
    // float → int заборонено ЗАВЖДИ, навіть без strict_types
    $cart->addItem(7.0, 100.0);   // TypeError: Argument #1 must be of type int, float given
} catch (TypeError $e) {
    echo $e->getMessage();
}

try {
    $cart->itemCount = '10';      // типізована властивість: теж TypeError
} catch (TypeError $e) {
    echo $e->getMessage();
}

// Директива не чіпає type juggling поза оголошеними типами:
var_dump('5' + 5);                // int(10) — арифметика працює як раніше
var_dump('abc' == 0);             // false у PHP 8+, true у PHP 7 — це вже інші правила

// Межа з HTTP: вхід завжди рядок, конвертація має бути явною
$page = filter_var($_GET['page'] ?? '1', FILTER_VALIDATE_INT) ?: 1;
$cart->addItem($page, 9.99);      // сюди приходить справжній int
Що `declare(strict_types=1)` діє на файл, у якому написаний, і не успадковується ані підключеними файлами, ані автозавантаженими класами — це не налаштування рантайму й не ini-опція.
Що вирішує файл **виклику**, а не файл оголошення функції: `strlen(42)` зі strict-файлу кинеться, хоча `strlen()` оголошена в ядрі.
Що директива стосується лише скалярних типів (`int`, `float`, `string`, `bool`) у параметрах, поверненнях і присвоєннях типізованим властивостям; `array`, обʼєкти, `iterable`, `callable` ніколи не приводяться і без неї.
Що єдине послаблення в strict-режимі — widening `int` → `float` (передати `5` у параметр `float` можна), а `float` → `int` заборонено завжди.
Що з PHP 8.0 coercive-режим став суворішим: `'abc'` в `int` — це `TypeError`, `'5 apples'` — `5` з `E_WARNING`, `'5'` — тихо `5`; до 8.0 `'abc'` мовчки ставало `0`.
Що `declare(strict_types=1)` має стояти першою інструкцією файлу (після `<?php`, до всього іншого, крім опційного блоку `declare` у фігурних дужках) — інакше фатальна помилка компіляції.
Ставити `declare(strict_types=1)` в один `index.php`/`bootstrap.php` і вважати, що весь проєкт тепер строгий: директива не поширюється на `include`, `require` й автозавантажені класи.
Казати, що strict_types вимикає «type juggling» узагалі: `'5' + 5`, `if ($str)`, `==` і `array_sum()` працюють точно так само — директива стосується лише перевірки оголошених типів.
Вважати, що директива діє там, де функція оголошена. Насправді режим бере з файлу, у якому стоїть виклик; бібліотека без strict, викликана зі strict-файлу, отримає строгу перевірку.
Думати, що в strict-режимі `int` не пройде в параметр `float` — widening дозволений і це навмисно, бо в PHP немає літерала для «цілого float».
Розраховувати на strict_types у даних із HTTP: `$_GET['page']` — завжди рядок, і в strict-режимі його треба явно кастити (`(int)`) або валідувати `filter_var`, а не сподіватись, що PHP «сам зрозуміє».
Плутати `declare(strict_types=1)` з `1` як «увімкнено для всіх» — допустимі лише літерали `0` і `1`, змінна чи константа там викличе помилку компіляції.
ПОРАДА

Формулювання, яке закриває питання: «strict_types — це директива компіляції на один файл, і вирішує вона файл виклику; вмикає не заборону type juggling, а заборону неявного приведення скалярів у типізованих сигнатурах — плюс один виняток, int → float». Далі додайте, що в проєкті це ставлять у кожен PHP-файл автоматично (Pint/PHP-CS-Fixer, правило `declare_strict_types`), а на межі з HTTP усе одно потрібен явний каст.

оновлено 3 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

`strict_types` — директива компіляції з областю дії в один файл; вона не успадковується через `include`/автозавантаження. Перевірка аргументів іде за режимом файлу, у якому знаходиться сам виклик, тому строгий файл отримає `TypeError` навіть від «нестрогої» бібліотеки. Арифметичне приведення (`'5' + 5`) директива не зачіпає взагалі.