타입 저글링
타입 저글링 (Type Juggling)
PHP는 변수 선언에 명시적인 타입 정의를 요구하지 않아요. 이 페이지에서는 값이 어떤 타입인지에 따라 타입이 정해지는 방식과, 여러 문맥(context)에서 PHP가 값을 자동으로 다른 타입으로 변환하는 타입 저글링을 다뤄요.
본문
PHP는 변수 선언에 명시적인 타입 정의를 요구하지 않아요. 이 경우 변수의 타입은 저장하는 값에 의해 결정돼요. 즉, $var 변수에 문자열을 할당하면 $var는 string 타입이 되고, 이후에 int 값을 $var에 할당하면 int 타입이 돼요.
PHP는 특정 문맥에서 값의 타입을 자동으로 다른 타입으로 변환하려고 시도할 수 있어요. 존재하는 서로 다른 문맥은 다음과 같아요:
- Numeric (숫자)
- String (문자열)
- Logical (논리)
- Integral and string (정수 및 문자열)
- Comparative (비교)
- Function (함수)
참고: 값을 다른 타입으로 해석해야 할 때, 값 자체의 타입은 변하지 않아요.
변수를 특정 타입으로 강제 평가하게 하려면 타입 캐스팅 (Type casting) 섹션을 참고하고, 변수의 타입을 바꾸려면 settype() 함수를 참고해요.
숫자 문맥 (Numeric contexts)
이것은 산술 연산자를 사용할 때의 문맥이에요.
이 문맥에서 피연산자 중 하나가 float(또는 int로 해석할 수 없는 경우)이면 두 피연산자 모두 float로 해석되고 결과도 float가 돼요. 그렇지 않으면 피연산자가 int로 해석되고 결과도 int가 돼요. PHP 8.0.0부터 피연산자 중 하나를 해석할 수 없으면 TypeError가 던져져요.
문자열 문맥 (String contexts)
이것은 echo, print, 문자열 보간 또는 문자열 연결 연산자를 사용할 때의 문맥이에요.
이 문맥에서 값은 string으로 해석돼요. 값을 해석할 수 없으면 TypeError가 던져져요. PHP 7.4.0 이전에는 E_RECOVERABLE_ERROR가 발생했어요.
논리 문맥 (Logical contexts)
이것은 조건문, 삼항 연산자 또는 논리 연산자를 사용할 때의 문맥이에요.
이 문맥에서 값은 bool로 해석돼요.
정수 및 문자열 문맥 (Integral and string contexts)
이것은 비트 연산자를 사용할 때의 문맥이에요.
이 문맥에서 모든 피연산자가 string 타입이면 결과도 string이 돼요. 그렇지 않으면 피연산자가 int로 해석되고 결과도 int가 돼요. PHP 8.0.0부터 피연산자 중 하나를 해석할 수 없으면 TypeError가 던져져요.
비교 문맥 (Comparative contexts)
이것은 비교 연산자를 사용할 때의 문맥이에요.
이 문맥에서 일어나는 타입 변환은 다양한 타입과의 비교 (Comparison with Various Types) 표에 설명돼 있어요.
함수 문맥 (Function contexts)
이것은 값이 타입이 지정된 매개변수, 속성에 전달되거나 반환 타입을 선언한 함수에서 반환될 때의 문맥이에요.
이 문맥에서 값은 해당 타입의 값이어야 해요. 두 가지 예외가 존재해요. 첫 번째는 값이 int 타입이고 선언된 타입이 float라면 정수가 부동소수점 숫자로 변환된다는 점이에요. 두 번째는 선언된 타입이 스칼라 타입이고, 값이 스칼라 타입으로 변환 가능하며, 강제 타입 모드(coercive typing mode)가 활성화(기본값)돼 있을 때, 값이 허용되는 스칼라 값으로 변환될 수 있다는 점이에요. 이 동작에 대한 설명은 아래를 참고해요.
경고: 내부 함수는
null을 스칼라 타입으로 자동 강제 변환해요. 이 동작은 PHP 8.1.0부터 폐기됐어요.
단순 타입 선언으로 강제 타입 (Coercive typing with simple type declarations)
bool타입 선언: 값은bool로 해석돼요.int타입 선언: 변환이 잘 정의된 경우(예: 문자열이 숫자인 경우) 값은int로 해석돼요.float타입 선언: 변환이 잘 정의된 경우(예: 문자열이 숫자인 경우) 값은float로 해석돼요.string타입 선언: 값은string으로 해석돼요.
유니언 타입으로 강제 타입 (Coercive typing with union types)
strict_types가 활성화되지 않으면 스칼라 타입 선언은 제한된 암시적 타입 강제 변환을 받아요. 값의 정확한 타입이 유니언에 속하지 않으면, 대상 타입은 다음 우선순위 순서로 선택돼요:
intfloatstringbool
타입이 유니언에 존재하고 값이 PHP의 기존 타입 검사 의미론에 따라 그 타입으로 강제 변환될 수 있으면 그 타입이 선택돼요. 그렇지 않으면 다음 타입이 시도돼요.
주의: 예외로, 값이 문자열이고
int와float가 모두 유니언에 있을 때는 기존 숫자 문자열 의미론에 따라 선호 타입이 결정돼요. 예를 들어"42"는int가,"42.0"은float가 선택돼요.
참고: 위 우선순위 목록에 없는 타입은 암시적 강제 변환의 대상이 될 수 없어요. 특히
null,false,true타입으로의 암시적 강제 변환은 발생하지 않아요.
예제 #1 유니언의 일부인 타입으로 강제 변환되는 예
<?php
// int|string
42 --> 42 // exact type
"42" --> "42" // exact type
new ObjectWithToString --> "Result of __toString()"
// object never compatible with int, fall back to string
42.0 --> 42 // float compatible with int
42.1 --> 42 // float compatible with int
1e100 --> "1.0E+100" // float too large for int type, fall back to string
INF --> "INF" // float too large for int type, fall back to string
true --> 1 // bool compatible with int
[] --> TypeError // array not compatible with int or string
// int|float|bool
"45" --> 45 // int numeric string
"45.0" --> 45.0 // float numeric string
"45X" --> true // not numeric string, fall back to bool
"" --> false // not numeric string, fall back to bool
"X" --> true // not numeric string, fall back to bool
[] --> TypeError // array not compatible with int, float or bool
?>
타입 캐스팅 (Type Casting)
타입 캐스팅은 변환할 값 앞에 괄호 안에 타입을 적어서 값을 선택한 타입으로 변환해요.
예제 #2 타입 캐스팅
<?php
$foo = 10; // $foo is an integer
$bar = (bool) $foo; // $bar is a boolean
var_dump($bar);
?>
허용되는 캐스트는 다음과 같아요:
(int)- int로 캐스트(bool)- bool로 캐스트(float)- float로 캐스트(string)- string으로 캐스트(array)- array로 캐스트(object)- object로 캐스트(unset)- NULL로 캐스트
(void) 캐스트도 PHP 8.5.0부터 사용할 수 있지만, 이것은 값 변환이 아니에요. 표현식의 결과를 명시적으로 버리는 문으로 사용돼요.
경고:
(integer)는(int)캐스트의 별칭이에요.(boolean)은(bool)캐스트의 별칭이고,(binary)는(string)캐스트의 별칭이며,(double)과(real)은(float)캐스트의 별칭이에요. 이 캐스트들은 표준 타입 이름을 사용하지 않으며 PHP 8.5.0부터 폐기됐어요.
경고:
(real)캐스트 별칭은 PHP 7.4.0부터 폐기됐고 PHP 8.0.0에서 제거됐어요.
경고:
(unset)캐스트는 PHP 7.2.0부터 폐기됐어요.(unset)캐스트는 변수나 호출에NULL값을 할당하는 것과 같다는 점에 주의하세요.(unset)캐스트는 PHP 8.0.0에서 제거됐어요.
주의:
(binary)캐스트와b접두사는 전방 지원을 위해 존재해요. 현재(binary)와(string)은 동일하지만, 이것은 변경될 수 있으므로 의존해서는 안 돼요.
참고: 캐스트의 괄호 안의 공백은 무시돼요. 따라서 다음 두 캐스트는 동일해요:
<?php
$foo = (int) $bar;
$foo = ( int ) $bar;
?>
리터럴 문자열과 변수를 binary 문자열로 캐스팅:
<?php
$binary = (binary) $string;
$binary = b"binary string";
?>
변수를 문자열로 캐스팅하는 대신, 변수를 큰따옴표로 감싸는 것도 가능해요.
예제 #3 서로 다른 캐스팅 메커니즘
<?php
$foo = 10; // $foo is an integer
$str = "$foo"; // $str is a string
$fst = (string) $foo; // $fst is also a string
// This prints out that "they are the same"
if ($fst === $str) {
echo "they are the same", PHP_EOL;
}
?>
특정 타입 사이에서 캐스팅할 때 정확히 무슨 일이 일어나는지는 명확하지 않을 수 있어요. 자세한 내용은 다음 섹션들을 참고해요:
- 불리언으로 변환 (Converting to boolean)
- 정수로 변환 (Converting to integer)
- 부동소수점으로 변환 (Converting to float)
- 문자열로 변환 (Converting to string)
- 배열로 변환 (Converting to array)
- 객체로 변환 (Converting to object)
- 리소스로 변환 (Converting to resource)
- NULL로 변환 (Converting to NULL)
- (void)로 값 버리기 (Discarding a value with (void))
- 타입 비교 표 (The type comparison tables)
참고: PHP는 배열 인덱싱과 같은 문법을 사용해서 문자열의 오프셋 인덱싱을 지원하므로, 다음 예제는 모든 PHP 버전에서 성립해요:
예제 #4 문자열에 배열 오프셋 사용
<?php
$a = 'car'; // $a is a string
$a[0] = 'b'; // $a is still a string
echo $a; // bar
?>
자세한 내용은 문자로 문자열 접근 (String access by character) 섹션을 참고해요.