타입 선언

타입 선언 (Type declarations)

타입 선언은 함수 인자, 반환 값, (PHP 7.4.0부터) 클래스 속성, (PHP 8.3.0부터) 클래스 상수에 추가할 수 있어요. 이것들은 호출 시점에 값이 지정된 타입인지 보장하며, 그렇지 않으면 TypeError가 던져져요. 이 페이지에서는 각 타입의 사용 가능 시점(체인지로그)과 타입 선언에서의 사용법을 다뤄요.

출처: PHP: Type declarations - Manual

본문

타입 선언은 함수 인자, 반환 값, (PHP 7.4.0부터) 클래스 속성, (PHP 8.3.0부터) 클래스 상수에 추가할 수 있어요. 이것들은 호출 시점에 값이 지정된 타입인지 보장하며, 그렇지 않으면 TypeError가 던져져요.

resource를 제외한 PHP가 지원하는 모든 단일 타입은 사용자 영역(user-land) 타입 선언에서 사용할 수 있어요. 이 페이지에는 다양한 타입의 사용 가능 시점에 대한 체인지로그와 타입 선언에서의 사용법에 대한 문서가 들어 있어요.

참고: 클래스가 인터페이스 메서드를 구현하거나 부모 클래스가 이미 정의한 메서드를 다시 구현할 때, 앞서 정의된 정의와 호환되어야 해요. 메서드가 분산 규칙(variance rules)을 따른다면 호환돼요.

체인지로그 (Changelog)

Version Description
8.3.0 클래스, 인터페이스, 트레이트, 열거형 상수 타입 지원이 추가됨
8.2.0 DNF 타입 지원이 추가됨
8.2.0 리터럴 타입 true 지원이 추가됨
8.2.0 nullfalse 타입을 단독으로 사용할 수 있게 됨
8.1.0 교차 타입(intersection types) 지원이 추가됨
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 nullable 타입 지원이 추가됨

원자 타입 사용 참고 (Atomic Types Usage Notes)

원자 타입(Atomic types)은 직관적인 동작을 가지지만, 이 섹션에서 설명하는 몇 가지 사소한 주의점이 있어요.

스칼라 타입 (Scalar types)

경고: 스칼라 타입(bool, int, float, string)의 이름 별칭은 지원되지 않아요. 대신 클래스 또는 인터페이스 이름으로 취급돼요. 예를 들어 타입 선언으로 boolean을 사용하면 값이 bool 타입이 아니라 boolean이라는 클래스나 인터페이스의 인스턴스여야 한다는 뜻이 돼요:

<?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를 발생시켰어요: Only variable references should be returned by reference.

<?php
function &test(): void {}
?>

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

복합 타입 사용 참고 (Composite Types Usage Notes)

복합 타입(Composite types) 선언은 몇 가지 제약을 받으며, 단순한 버그를 막기 위해 컴파일 시간에 중복 검사를 수행해요.

주의: PHP 8.2.0 이전, DNF 타입이 도입되기 전에는 교차 타입과 유니언 타입을 결합하는 것이 불가능했어요.

유니언 타입 (Union types)

경고: 두 싱글턴 타입 falsetrue를 유니언 타입에서 함께 결합하는 것은 불가능해요. 대신 bool을 사용하세요.

주의: PHP 8.2.0 이전에는 falsenull을 단독 타입으로 사용할 수 없었기 때문에, 이 타입들만으로 이루어진 유니언 타입은 허용되지 않았어요. 여기에는 false, false|null, ?false 타입이 포함돼요.

Nullable 타입 문법 설탕 (Nullable type syntactic sugar)

단일 기본 타입 선언은 타입 앞에 물음표(?)를 붙여 nullable로 표시할 수 있어요. 따라서 ?TT|null과 동일해요.

참고: 이 문법은 PHP 7.1.0부터 지원되며, 일반화된 유니언 타입 지원보다 앞선 것이에요.

참고: null을 기본값으로 만들어 nullable 인자를 구현하는 것도 가능해요. 하지만 이는 권장되지 않아요. 하위 클래스에서 기본값이 변경되면 타입 호환성 위반이 발생하는데, 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

중복 및 중복된 타입 (Duplicate and redundant types)

복합 타입 선언의 단순한 버그를 잡기 위해, 클래스 로딩 없이 감지할 수 있는 중복 타입은 컴파일 타임 오류를 일으켜요. 여기에는 다음이 포함돼요:

  • 각 이름이 해석된 타입은 한 번만 나타날 수 있어요. int|string|INTCountable&Traversable&COUNTABLE 같은 타입은 오류를 일으켜요.
  • mixed 또는 never를 사용하면 오류를 일으켜요.
  • 유니언 타입의 경우:
    • bool을 사용하면 falsetrue를 추가로 사용할 수 없어요.
    • object를 사용하면 클래스 타입을 추가로 사용할 수 없어요.
    • iterable을 사용하면 arrayTraversable을 추가로 사용할 수 없어요.
  • 교차 타입의 경우:
    • 클래스 타입이 아닌 타입을 사용하면 오류를 일으켜요.
    • self, parent, static을 사용하면 오류를 일으켜요.
  • DNF 타입의 경우:
    • 더 일반적인 타입을 사용하면 더 제한적인 타입은 중복돼요.
    • 두 개의 동일한 교차 타입을 사용하면 안 돼요.

참고: 이는 타입이 "최소(minimal)"임을 보장하지는 않아요. 그렇게 하려면 사용된 모든 클래스 타입을 로딩해야 하기 때문이에요.

예를 들어 AB가 클래스 별칭이면, A|BAB로 줄일 수 있음에도 여전히 합법적인 유니언 타입이에요. 마찬가지로 class B extends A {}라면, A|BA로만 줄일 수 있음에도 합법적인 유니언 타입이에요.

<?php
function foo(): int|INT {} // Disallowed
function foo(): bool|false {} // Disallowed
function foo(): int&Traversable {} // Disallowed
function foo(): self&Traversable {} // Disallowed

use A as B;
function foo(): A|B {} // Disallowed ("use" is part of name resolution)
function foo(): A&B {} // Disallowed ("use" is part of name resolution)

class_alias('X', 'Y');
function foo(): X|Y {} // Allowed (redundancy is only known at runtime)
function foo(): X&Y {} // Allowed (redundancy is only known at runtime)
?>

예제 (Examples)

예제 #3 기본 클래스 타입 선언

<?php
class C {}
class D extends C {}

// This doesn't extend 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() {} }

// This doesn't implement 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;
}

// Note that a float will be returned.
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 Nullable 인자 타입 선언

<?php
class C {}

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

f(new C);
f(null);
?>

위 예제의 출력:

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

예제 #8 Nullable 반환 타입 선언

<?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;
    }
}
?>

엄격 타입 (Strict typing)

기본적으로 PHP는 가능하면 잘못된 타입의 값을 기대되는 스칼라 타입 선언으로 강제 변환(coerce)해요. 예를 들어 문자열을 기대하는 매개변수에 int를 주는 함수는 string 타입의 변수를 받게 돼요.

파일 단위로 엄격 모드(strict mode)를 활성화할 수 있어요. 엄격 모드에서는 타입 선언과 정확히 일치하는 값만 받아들여지며, 그렇지 않으면 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));

// These will be coerced to integers: note the output below!
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

더 알아보기