PHP 8.4.6 Released!

Объявления типов

Объявления типов добавляют параметрам функций, значениям возврата, начиная с PHP 7.4.0 свойствам класса и начиная с PHP 8.3.0 константам класса. Объявления типов гарантируют, что при вызове значение принадлежит заданному типу, иначе PHP выбросит ошибку TypeError.

Каждый тип, который поддерживает PHP, за исключением ресурсов (resource), разрешается указывать в пользовательском объявлении типа. Страница содержит журнал изменений доступности отдельных типов и документацию, которая описывает правила объявления типов.

Замечание:

Класс, который реализует метод интерфейса или переопределяет метод родительского класса, подчиняется правилам совместимости. Метод совместим, если следует правилам вариантности.

Список изменений

Версия Описание
8.3.0 Добавили поддержку типизации констант классов, интерфейсов, трейтов и перечислений.
8.2.0 Добавили поддержку DNF-типов.
8.2.0 Добавили поддержку самостоятельного типа true.
8.2.0 Типы null и false разрешили указывать отдельно.
8.1.0 Добавили поддержку пересечений типов.
8.1.0 Возврат по ссылке из функции с типом значения возврата void устарел.
8.1.0 Для значения возврата добавили поддержку типа never.
8.0.0 Для значения возврата добавили поддержку типа mixed.
8.0.0 Для значения возврата добавили поддержку типа static.
8.0.0 Добавили поддержку объединения типов.
7.4.0 Добавили поддержку типизации свойств классов.
7.2.0 Для значения возврата добавили поддержку типа object.
7.1.0 Для значения возврата добавили поддержку типа iterable.
7.1.0 Для значения возврата добавили поддержку типа void.
7.1.0 Для значения возврата добавили поддержку обнуляемого типа.

Атомарные типы

Атомарные типы ведут себя прямолинейно, но с рядом оговорок, которые описывает этот раздел.

Скалярные типы

Внимание

Псевдонимы имён для скалярных типов, к которым относятся типы bool, int, float и string, не поддерживаются. Вместо назначения псевдонимов названия классов и интерфейсов трактуются как типы. Например, буквальное название boolean в объявлении типа потребует, чтобы значение выполняло условие instanceof в отношении класса или интерфейса boolean, а не принадлежало типу bool:

<?php

function test(boolean $param) {}
test(true);

?>

Результат выполнения приведённого примера в PHP 8:

Warning: "boolean" will be interpreted as a class name. Did you mean "bool"? Write "\boolean" to suppress this warning in /in/9YrUX on line 2

Fatal error: Uncaught TypeError: test(): Argument #1 ($param) must be of type boolean, bool given, called in - on line 3 and defined in -:2
Stack trace:
#0 -(3): test(true)
#1 {main}
  thrown in - on line 2

Тип void

Замечание:

Возврат по ссылке из void-функции устарел начиная с PHP 8.1.0, поскольку такая функция противоречива. Раньше при вызове такой функции уже выдавалась ошибка уровня E_NOTICE: Только ссылки на переменные должны возвращаться по ссылке.

<?php

function &test(): void {}

?>

Тип Callable

Свойствам класса нельзя объявлять тип callable.

Замечание: Тип callable нельзя указывать как название функции.

Объявление типов параметрам, которые принимают ссылку

Параметр, который принимает ссылку, проверяет тип переменной только при входе в функцию, в начале вызова, но не при возврате из функции, поэтому в функции возможно изменение типа ссылки на переменную.

Пример #1 Типизированный параметр, который принимает ссылку

<?php

function array_baz(array &$param)
{
$param = 1;
}

$var = [];
array_baz($var);
var_dump($var);
array_baz($var);

?>

Вывод приведённого примера будет похож на:

int(1)

Fatal error: Uncaught TypeError: array_baz(): Argument #1 ($param) must be of type array, int given, called in - on line 9 and defined in -:2
Stack trace:
#0 -(9): array_baz(1)
#1 {main}
  thrown in - on line 2

Примечание по составным типам

На объявления составных типов распространяется ряд ограничений; компилятор выполняет проверку избыточности типов, чтобы предотвратить простые ошибки.

Предостережение

Комбинирование пересечений и объединений типов оставалось невозможным до PHP 8.2.0 и введения DNF-типов.

Объединение типов

Внимание

Невозможно определить в объединённом типе два одноэлементных типа false и true. Вместо этого указывают тип bool.

Предостережение

До PHP 8.2.0 запрещали определять типы false и null отдельно, поэтому объединение типов, которое состояло только из этих типов, оставалось недопустимым. К таким объединениям относятся типы: false, false|null и ?false.

Синтаксический сахар обнуляемого типа

Объявление отдельного базового типа помечают как nullable путём добавления к типу префикса в виде вопросительного знака (?). Поэтому типы ?T и T|null идентичны.

Замечание: Этот синтаксис поддерживается с PHP 7.1.0 и предшествует поддержке объединения типов.

Замечание:

Ещё один способ объявить nullable-параметр — указать значением по умолчанию значение null. Объявлять тип таким способом не рекомендуют, поскольку изменение значения по умолчанию в дочернем классе нарушит совместимость типов, и к объявлению типа потребуется добавить тип null. Это поведение также устарело начиня с PHP 8.4.

Пример #2 Старый способ указать nullable-параметр

<?php

class C {}

function
f(C $c = null)
{
var_dump($c);
}

f(new C());
f(null);

?>

Результат выполнения приведённого примера:

object(C)#1 (0) {
}
NULL

Повторные и избыточные типы

Правила объявления составных типов предусматривают ряд ограничений. При компиляции кода с избыточными типами, которые возможно обнаружить без загрузки классов, возникнет ошибка. Примеры ошибок в объявлениях составных типов включают:

  • Повторение типа в объявлениях наподобие int|string|INT или Countable&Traversable&COUNTABLE, поскольку после разрешения названия типа внутренними средствами языка тип встречается больше одного раза.
  • Избыточное объявление типа mixed с другими типами.
  • Ошибки объединения типов:
    • Избыточное объявление типа false или true вместе с типом bool.
    • Избыточное объявление типа класса вместе с типом object.
    • Избыточное объявление типа array или Traversable вместе с типом iterable.
  • Ошибки пересечения типов:
    • Объявление типа, который не относится к классовому типу.
    • Объявление типа self, parent или static.
  • Ошибки объявления DNF-типов:
    • Избыточное объявление более строго типа с более общим типом.
    • Объявление дубликатов в пересечении типов.

Замечание: Эти проверки не гарантируют, что тип «минимален» и не содержит повторений или избыточных типов, поскольку для этого потребовалось бы загрузить все указанные типы классов.

Например, если типы A и B — псевдонимы классов, то составной тип A|B остаётся корректным объединением типов, даже если тип получится свести либо к A, либо к B. Аналогично, если класс B extends A {}, то составной тип A|B также относится к корректному объединению типов, даже если тип получится свести только к типу A.

<?php

function foo(): int|INT {} // Запрещено
function foo(): bool|false {} // Запрещено
function foo(): int&Traversable {} // Запрещено
function foo(): self&Traversable {} // Запрещено

use A as B;
function
foo(): A|B {} // Запрещено (оператор "use" только создаёт псевдоним через механизм разрешения имён, а не отдельный тип)
function foo(): A&B {} // Запрещено (оператор "use" только создаёт псевдоним через механизм разрешения имён, а не отдельный тип)

class_alias('X', 'Y');
function
foo(): X|Y {} // Разрешено (избыточность обнаружится только при выполнении кода)
function foo(): X&Y {} // Разрешено (избыточность обнаружится только при выполнении кода)

?>

Примеры

Пример #3 Пример объявления типа через название класса

<?php

class C {}
class
D extends C {}

// Класс не наследует класс C
class E {}

function
f(C $c)
{
echo
get_class($c)."\n";
}

f(new C);
f(new D);
f(new E);

?>

Результат выполнения приведённого примера в PHP 8:

C
D

Fatal error: Uncaught TypeError: f(): Argument #1 ($c) must be of type C, E given, called in /in/gLonb on line 14 and defined in /in/gLonb:8
Stack trace:
#0 -(14): f(Object(E))
#1 {main}
  thrown in - on line 8

Пример #4 Пример объявления типа через название интерфейса

<?php

interface I
{
public function
f();
}

class
C implements I
{
public function
f() {}
}

// Класс не реализует интерфейс I
class E {}

function
f(I $i)
{
echo
get_class($i) . "\n";
}

f(new C);
f(new E);

?>

Результат выполнения приведённого примера в PHP 8:

C

Fatal error: Uncaught TypeError: f(): Argument #1 ($i) must be of type I, E given, called in - on line 13 and defined in -:8
Stack trace:
#0 -(13): f(Object(E))
#1 {main}
  thrown in - on line 8

Пример #5 Пример объявления типа значения возврата

<?php

function sum($a, $b): float
{
return
$a + $b;
}

// Обратите внимание, что функция вернёт значение с типом float
var_dump(sum(1, 2));

?>

Результат выполнения приведённого примера:

float(3)

Пример #6 Возврат объекта

<?php

class C {}

function
getC(): C
{
return new
C();
}

var_dump(getC());

?>

Результат выполнения приведённого примера:

object(C)#1 (0) {
}

Пример #7 Объявление параметра с обнуляемым типом

<?php

class C {}

function
f(?C $c)
{
var_dump($c);
}

f(new C());
f(null);

?>

Результат выполнения приведённого примера:

object(C)#1 (0) {
}
NULL

Пример #8 Объявление типа значения возврата как обнуляемого

<?php

function get_item(): ?string
{
if (isset(
$_GET['item'])) {
return
$_GET['item'];
} else {
return
null;
}
}

?>

Пример #9 Объявление типа свойства класса

<?php

class User
{
public static
string $foo = 'foo';

public
int $id;
public
string $username;

public function
__construct(int $id, string $username)
{
$this->id = $id;
$this->username = $username;
}
}

?>

Строгая типизация

В нестрогом режиме, в котором проверка типов работает по умолчанию, PHP преобразовывает значения неправильного типа в скалярный тип, который указали в объявлении, поэтому, например, при передаче в параметр с типом string аргумента с типом int функция получит значение с типом string.

Строгий режим включается отдельно для каждого файла. В строгом режиме механизм проверки типов признаёт только значения, которые соответствуют типу объявления, иначе выбрасывается ошибка TypeError. Единственное исключение из правила — значения с типом int, которые передают в объявления типа float.

Внимание

Объявление директивы strict_types не влияет на вызовы функций внутри встроенных функций.

Строгий режим включают инструкцией declare с объявлением директивы strict_types:

Замечание:

На строгость проверки типов при вызове функций влияет режим, который объявили внутри файла вызова, а не режим, который установили в файле объявления функции. Механизм проверки типов применит режим, который установили на стороне вызова, и автоматически приведёт типы значений, если файл без строгой типизации вызывает функцию, которую объявили в файле со строгой типизацией.

Замечание:

Строгая типизация определяется только для объявлений скалярных типов.

Пример #10 Строгая типизация для значений аргументов

<?php

declare(strict_types=1);

function
sum(int $a, int $b)
{
return
$a + $b;
}

var_dump(sum(1, 2));
var_dump(sum(1.5, 2.5));

?>

Результат выполнения приведённого примера в PHP 8:

int(3)

Fatal error: Uncaught TypeError: sum(): Argument #1 ($a) must be of type int, float given, called in - on line 9 and defined in -:4
Stack trace:
#0 -(9): sum(1.5, 2.5)
#1 {main}
  thrown in - on line 4

Пример #11 Приведение типов для значений аргументов

<?php

function sum(int $a, int $b)
{
return
$a + $b;
}

var_dump(sum(1, 2));

// Механизм проверки типов приведёт значения аргументов к целым числам: обратите внимание на вывод!
var_dump(sum(1.5, 2.5));

?>

Результат выполнения приведённого примера:

int(3)
int(3)

Пример #12 Строгая типизация для значений возврата

<?php

declare(strict_types=1);

function
sum($a, $b): int
{
return
$a + $b;
}

var_dump(sum(1, 2));
var_dump(sum(1, 2.5));

?>

Результат выполнения приведённого примера:

int(3)

Fatal error: Uncaught TypeError: sum(): Return value must be of type int, float returned in -:5
Stack trace:
#0 -(9): sum(1, 2.5)
#1 {main}
  thrown in - on line 5
Добавить

Примечания пользователей 3 notes

up
26
toinenkayt (ta at ta) [iwonderr] gmail d
3 years ago
While waiting for native support for typed arrays, here are a couple of alternative ways to ensure strong typing of arrays by abusing variadic functions. The performance of these methods is a mystery to the writer and so the responsibility of benchmarking them falls unto the reader.

PHP 5.6 added the splat operator (...) which is used to unpack arrays to be used as function arguments. PHP 7.0 added scalar type hints. Latter versions of PHP have further improved the type system. With these additions and improvements, it is possible to have a decent support for typed arrays.

<?php
declare (strict_types=1);

function
typeArrayNullInt(?int ...$arg): void {
}

function
doSomething(array $ints): void {
(function (?
int ...$arg) {})(...$ints);
// Alternatively,
(fn (?int ...$arg) => $arg)(...$ints);
// Or to avoid cluttering memory with too many closures
typeArrayNullInt(...$ints);

/* ... */
}

function
doSomethingElse(?int ...$ints): void {
/* ... */
}

$ints = [1,2,3,4,null];
doSomething ($ints);
doSomethingElse (...$ints);
?>

Both methods work with all type declarations. The key idea here is to have the functions throw a runtime error if they encounter a typing violation. The typing method used in doSomethingElse is cleaner of the two but it disallows having any other parameters after the variadic parameter. It also requires the call site to be aware of this typing implementation and unpack the array. The method used in doSomething is messier but it does not require the call site to be aware of the typing method as the unpacking is performed within the function. It is also less ambiguous as the doSomethingElse would also accept n individual parameters where as doSomething only accepts an array. doSomething's method is also easier to strip away if native typed array support is ever added to PHP. Both of these methods only work for input parameters. An array return value type check would need to take place at the call site.

If strict_types is not enabled, it may be desirable to return the coerced scalar values from the type check function (e.g. floats and strings become integers) to ensure proper typing.
up
18
crash
3 years ago
The documentation lacks the information, that it's possible to change the return type of a method defined in an interface when the interface's methods return type is defined as `mixed`.

From the RFC:

"The mixed return type could be narrowed in a subclass as this is covariant and is allowed in LSP." (https://wiki.php.net/rfc/mixed_type_v2)

This means the following code is valid in PHP 8.0:

<?php

interface ITest
{
public function
apfel(): mixed; // valid as of 8.0
}

class
Test implements ITest
{
public function
apfel(): array // more explicit
{
return [];
}
}

var_dump((new Test())->apfel());
?>

You can see the result here: https://3v4l.org/PXDB6
up
1
harl at gmail dot com
3 months ago
For DNF type declarations (which lack an example), they're a mix of intersection and union types that look like this:
<?php

function send(c1|(c2&c3) $f) {}

?>

It's a union type where some of its options are intersection types, which are wrapped in parentheses ($f is something that is either a c1, or something that is both a c2 and a c3).
To Top