PHP 부록에 있는 이주 문서를 읽으면서 정리했다. 완전한 번역은 아니며 중요도가 높다고 생각되는 부분을 주로 정리했다. 세세한 부분이나 함수는 각각 문서를 참고하는 것을 권장한다.
목차
- PHP 5.6 (2014년 8월, 공식 문서)
- PHP 7.0 (2015년 12월, 공식 문서)
- PHP 7.1 (2016년 12월, 공식 문서)
- PHP 7.2 (2017년 11월, 공식 문서)
- PHP 7.3 (2018년 12월, 공식 문서)
- PHP 7.4 (2019년 11월, 공식 문서)
- PHP 8.0 (2020년 11월, 공식 문서)
- PHP 8.1 (2021년 11월, 공식 문서)
PHP 5.6
호환성 문제 있는 변경
엄격해진 json_decode()
json_decode()
에서 소문자가 아닌 true
, false
, null
JSON 리터럴을 사용한 경우에는 오류가 발생하도록 변경되었다. 오류는 json_last_error()
로 확인 가능하다.
$json = '{
"is_available": TRUE
}';
$response = json_decode($json);
json_last_error() === JSON_ERROR_SYNTAX; // true
새 기능
상수(constant) 표현식
숫자나 문자열 리터럴, 배열을 상수로 정의할 수 있다.
const ONE = 1;
const TWO = ONE * 2;
const ARR = [ONE, TWO];
class C {
const THREE = TWO + 1;
const ONE_THIRD = ONE / self::THREE;
const SENTENCE = 'The value of THREE is ' . self::THREE;
public function f($a = ONE + self::THREE) {
echo self::SENTENCE;
return $a;
}
}
echo (new C)->f(); // 4
echo C::SENTENCE; // 'The value of THREE is 3'
var_dump(ARR); // [1, 2]
개체도 사용할 수 있다.
class Person {
// ...
}
const ME = new Person('Edward');
var_dump(ME);
// object(Person)#1 (1) {
// ["name":protected]=>
// string(6) "Edward"
// }
ME = new Person('Yong');
// Parse error: syntax error, unexpected token "="
...
연산자 (operator)
함수에서 가변 인자 목록 받기
function school($name, $location = null, ...$students) {
printf('$name: %s, $location: %s, number of students: %d',
$name, $location, count($students));
}
school('Hogwarts School', 'Scotland', 'Harry', 'Ron', 'Hermione');
// $name: Hogwarts School, $location: Scotland, number of students: 3
인자 풀어넣기
배열이나 Traversable
개체를 대상으로 인자를 풀어놓을 때 ...
연산자를 사용할 수 있다. (다른 언어에서는 splat 연산자로 지칭) 이런 문제는 call_user_func_array()
같은 함수로 해결했었는데 더 간단하고 깔끔하게 작성할 수 있게 되었다.
function add($a, $b, $c) {
return $a + $b + $c;
}
$nums = [2, 3];
echo add(1, ...$nums); // 6
**
연 산자로 거듭제곱하기
$a = 2 ** 3; // 8
$b = 2;
$b **= 2; // 4
다음 연산 순서를 주의하자.
$a = 2 ** 3 ** 2;
$b = (2 ** 3) ** 2;
$c = 2 ** (3 ** 2);
// $a: 512
// $b: 64
// $c: 512
use function
, use const
함수나 상수도 use
연산자로 불러 사용할 수 있다.
namespace Hello\App {
const NAME = 'hello';
function study() { echo __FUNCTION__; }
}
namespace {
use const Hello\App\NAME;
use function Hello\App\study;
echo NAME; // 'hello'
study(); // 'Hello\App\study'
}
기본 문자열 인코딩
htmlentities()
, html_entity_decode()
, htmlspecialchars()
함수에서 기본 문자열 인코딩을 php.ini에 default_charset
값을 사용한다. 해당 설정은 UTF-8
이 기본값이다.
hash_equals()
시간 차 공격에 안전한 문자열 비교 함수
$expected = crypt('some-password', 'some-unsafe-salt');
$correct = crypt('some-password', 'some-unsafe-salt');
$incorrect = crypt('some-wrong-password', 'some-unsafe-salt');
hash_equals($expected, $correct); // true
hash_equals($expected, $incorrect); // false
비밀번호 관련 함수를 사용할 수 있다면 다음처럼 작성하는 것을 권장한다.
$hash = password_hash('some-password', PASSWORD_DEFAULT);
password_verify('some-password', $hash); // true
password_verify('some-wrong-password', $hash); // false
참고로 password_verify()
함수는 crypt()
함수의 반환값과도 사용할 수 있다.
__debugInfo()
매직 메소드
클래스에 __debugInfo()
를 정의하면 var_dump()
출력을 제어할 수 있다.
class Person {
private $name;
private $secret;
public function __construct($name, $secret) {
$this->name = $name;
$this->secret = $secret;
}
public function __debugInfo() {
return [
'name' => $this->name,
'secret' => '****',
];
}
}
$ed = new Person('Edward', 'have a national treasure');
var_dump($ed);
// object(Person)#1 (2) {
// ["name"]=>
// string(6) "Edward"
// ["secret"]=>
// string(4) "****"
// }
함수 변경점
crypt()
함수 호출 시salt
파라미터가 누락되면 E_NOTICE가 발생.substr_compare()
에length
파라미터로0
을 넣을 수 있음.unserialize()
함수 호출 시 생성자 호출 이전에 직렬화된 데이터를 조작한 시도가 있는 경우 직렬화에 실패하게 됨.
PHP 7.0
호환성 문제 있는 변경
오류/예외 처리 변경
많은 수의 심각한 오류(fatal error)가 예외 처리 형태로 변경되었다. 이 오류 예외는 Error
클래스를 상속하며 Throwable
인터페이스를 구현하고 있다. 직접 구현한 핸들러가 Exception
만 받도록 되어 있다면 Error
를 처리하지 못해서 심각한 오류가 발생할 수 있다.
set_exception_handler()
Throwable
인터페이스를 활용할 수 있다. 호환성을 고려한다면 타입 선언을 제외한다.
// Will break because of `Error`
function handler(Exception $e) { /* ... */ }
set_exception_handler('handler');
// PHP 5 and 7 compatible.
function handler($e) { /* ... */ }
// PHP 7 only.
function handler(Throwable $e) { /* ... */ }
ParseError
eval()
함수에서 오류가 발생한 경우 ParseError
를 catch
로 잡아서 처리할 수 있게 되었다.
변수 사용 변경점
PHP 7부터 abstract syntax tree를 사용하고 있어서 이전에 불가능한 문법을 많이 구현할 수 있게 되었다. 대신 일관성을 유지하기 위해 몇 가지 해석이 달라지는 부분도 생겼다.
// 표현식
$$foo['bar']['baz']
// PHP 5 해석
${$foo['bar']['baz']}
// PHP 7+ 해석
($$foo)['bar']['baz']
// 표현식
$foo->$bar['baz']
// PHP 5 해석
$foo->{$bar['baz']}
// PHP 7+ 해석
($foo->$bar)['baz']
// 표현식
$foo->$bar['baz']()
// PHP 5 해석
$foo->{$bar['baz']}()
// PHP 7+ 해석
($foo->$bar)['baz']()
// 표현식
Foo::$bar['baz']()
// PHP 5 해석
Foo::{$bar['baz']}()
// PHP 7+ 해석
(Foo::$bar)['baz']()
이전과 같은 방식으로 동작하려면 {}
를 사용해서 의미를 더 명확하게 작성해야 한다.
list()
변경점
list()
함수는 원래 역순으로 입력했는데 이제 순서대로 입력된다. 다만 list()
함수의 세부 구현이 변경될 가능성이 있기 때문에 이 함수로 생성한 순서의 의존하는 구현은 권하지 않는다.
list($a[], $a[], $a[]) = [1, 2, 3];
var_dump($a); // [1, 2, 3]
더 이상 문자열을 배열로 변환하는데 사용할 수 없다. str_split()
을 대신 사용한다.
foreach
변경점
foreach
가 배열 커서를 변경하지 않음
$arr = [0, 1, 2];
foreach($arr as &$val) {
echo current($arr); // always 0
}
by-value, by-reference 동작 차이
by-value 반복은 주어진 배열을 복사해서 반복하기 때문에 길이 변화를 인식하지 못한다. 대신 by-reference 반복 중에는 배열에 추가된 항목도 인식한다.
// by-value
$arr = [0];
foreach ($arr as $val) {
var_dump($val);
$arr[1] = 1;
}
var_dump($arr);
// int(0)
// array(2) {
// [0]=>
// int(0)
// [1]=>
// int(1)
// }
// by-reference
$arr = [0];
foreach ($arr as &$val) {
var_dump($val);
$arr[1] = 1;
}
var_dump($arr);
// int(0)
// int(1) <-- 추가된 부분도 인식해서 반복 처리
// array(2) {
// [0]=>
// int(0)
// [1]=>
// int(1)
// }
Traversable
하지 않은 개체의 반복
이런 개체는 by-reference 배열에 반복하는 것과 동일하게 처리된다.
int 변경점
- 0으로 나누기: 이전엔
false
가 반환되며 E_WARNING이 발생했는데 이제는 float으로+INF
,-INF
,NAN
이 반환되며DivisionByZeroError
가 발생한다. - 음수 비트제어 오류:
1 >> -1
등에ArithmeticError
가 발생한다.
문자열 변경점
더 이상 Hexadecimal 문자열이 숫자로 취급되지 않는다.
"0x123" == "291"; // false
is_numeric("0x123"); // false
"0xe" + "0x1"; // 0
Hexadecimal 문자열을 정수로 변환하는 경우 filter_var()
를 활용할 수 있다.
$str = "0xffff";
$int = filter_var($str, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
if (false === $int) {
throw new Exception("Invalid integer.");
}
var_dump($int); // int(65535)
그 외 변경점 (일부)
- 동일 이름 함수 파라미터 사용 시 E_COMPILE_ERROR
switch
에 여러default
선언 시 E_COMPILE_ERROR$HTTP_RAW_POST_DATA
제거.php://input
스트림 사용할 것.
제거된 함수
call_user_method()
,call_user_method_array()
- ereg 함수
전체 목록은 문서 참조.
yield
우측 평가로 변경
echo yield -1;
// Was previously interpreted as
echo (yield) - 1;
// And is now interpreted as
echo yield (-1);
yield $foo or die;
// Was previously interpreted as
yield ($foo or die);
// And is now interpreted as
(yield $foo) or die;
새 기능
스칼라 타입 선언: 강제(coercive)와 엄격(strict)
함수 파라미터에 스칼라 타입을 넣는 경우 타입 선언에 따라서 동작 방식이 달라진다. php의 기본 동작은 "강제"로 되어 있다. 강제 모드에서는 타입 힌트에 맞춰서 값이 캐스팅되지만 엄격 모드에서는 타입 힌트에 맞지 않는 경우에 오류가 발생한다.
강제 동작 방식:
// coercive
// declare(strict_types=0); // 기본값임
function sumOfInts(int ...$ints)
{
var_dump($ints);
return array_sum($ints);
}
var_dump(sumOfInts(2, '3', 4.1));
// PHP Deprecated: Implicit conversion from float 4.1
// to int loses precision in app.php on line 4
// array(3) {
// [0]=>
// int(2)
// [1]=>
// int(3)
// [2]=>
// int(4)
// }
// int(9)
엄격 동작 방식:
// strict
declare(strict_types=1);
function sumOfInts(int ...$ints)
{
return array_sum($ints);
}
var_dump(sumOfInts(2, '3', 4.1));
// PHP Fatal error: Uncaught TypeError: sumOfInts():
// Argument #2 must be of type int, string given,
// called in app.php on line 10 and defined in
// app.php:4
//
// Stack trace:
// #0 app.php(10): sumOfInts(2, '3', 4.1)
// #1 {main}
// thrown in app.php on line 4
이런 동작 방식은 반환 타입 선언에도 적용된다. 엄격 모드는 실행하는 파일 기준으로 적용된다. 즉, 엄격 모드가 선언된 파일에서 엄격 모드로 선언하지 않은 파일을 불러와 함수를 호출하면 엄격 모드로 동작한다. 타 입 선언 문서.
// // util.php
function sumOfInts(int ...$ints)
{
return array_sum($ints);
}
// // app.php
declare(strict_types=1);
require_once __DIR__.'/util.php';
var_dump(sumOfInts(2, '3', 4.1));
// Fatal error: Uncaught TypeError: sumOfInts(): Argument #2
// must be of type int, string given, called in
// app.php on line 4 and defined in util.php:2
//
// Stack trace:
// #0 app.php(4): sumOfInts(2, '3', 4.1)
// #1 {main}
// thrown in util.php on line 2
반환 타입 선언
function arraySum(array ...$arrays): array
{
return array_map(function(array $array): int {
return array_sum($array);
}, $arrays);
}
var_dump(arraySum([1,2,3], [4,5,6], [7,8,9]));
// array(3) {
// [0]=>
// int(6)
// [1]=>
// int(15)
// [2]=>
// int(24)
// }
??
: null 병합 연산자
값이 있거나 null
이 아닌 경우에는 앞에 있는 연산을, 그 외에는 뒤에 있는 연산을 반환한다.
// 이전 방식
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
// null 병합 연산자로 동일한 결과
$username = $_GET['user'] ?? 'nobody';
// 연속해서도 사용 가능
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
<=>
: 우주선 연산자
주어진 a, b 표현식을 비교해서 a < b, a == b, a > b 에 각각 -1, 0, 1 을 반환한다.
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
define()
으로 배열 상수 선언 가능
const
로만 선언 가능했는데 define()
으로도 가능해졌다.
define('ANIMALS', ['dog', 'cat', 'brid']);
echo ANIMALS[1]; // 'cat'
익명 클래스
new class
로 익명 클래스를 선언할 수 있다. 아래 예시는 익명 클래스로 Logger
인터페이스를 구현해서 활용하는 예제다. 익명 클래스 문서.
interface Logger {
public function log(string $msg);
}
class Application {
private $logger;
public function getLogger(): Logger {
return $this->logger;
}
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
}
$app = new Application;
$app->setLogger(new class implements Logger {
public function log(string $msg) {
echo $msg;
}
});
var_dump($app->getLogger());
유니코드 탈출 문자 문법
\u{...}
형태 문자열을 유니코드로 처리한다.
echo "\u{2615}"; // ☕
Closure::call()
클로저에 맥락을 주입해야 할 때 사용할 수 있는 간편한 방식이다.
class A { private $x = 1; }
$getX = function () { return $this-> x; };
$a = new A;
// 이전 방식
$getXCB = $getX->bindTo($a, A::class); // 중간 단계의 클로저
echo $getXCB();
// 새 방식
echo $getX->call($a);
unserialize()
에서 필터링하기
신뢰할 수 있는 데이터만 역직렬화 할 수 있도록 필터가 추가되었다. 코드 삽입 공격을 막는데 도움이 된다.
// 모든 개체를 __PHP_Incomplete_Class 개체로 변환
$data = unserialize($foo, ["allowed_classes" => false]);
// MyClass와 MyClass2를 제외한 모든 개체를 __PHP_Incomplete_Class 개체로 변환
$data = unserialize($foo, ["allowed_classes" => ["MyClass", "MyClass2"]]);
// 기본 동작 방식 (파라미터 없는 것과 동일)
$data = unserialize($foo, ["allowed_classes" => true]);
IntlChar
: 유니코 드 문자 관련 클래스
IntlChar 문서 참조. Intl 확장이 설치되어 있어야 한다.
printf('%x', IntlChar::CODEPOINT_MAX); // 10ffff
echo IntlChar::charName('☕'); // HOT BEVERAGE
var_dump(IntlChar::ispunct('!')); // bool(true)
Expectations
assert()
함수의 하위 호환성 강화를 위해 도입된 기능이다. assert.exception
설정에 따라서 assert()
가 실패했을 경우에 예외를 던진다. 특히 예전 API는 문자열을 처리하는데 그쳤지만 이제는 언어 구조로 편입되어 단순히 문자열 검사나 불린 값 평가가 아닌, 제대로 된 표현식 검사가 이뤄진다.
// 예전에는 이랬다... 문자열을 `eval()`로 처리해서 검사
assert("$hello === false", "hello is false...");
// ini_set('assert.exception', 0); // default
assert(false, 'Some error message');
// PHP Warning: assert(): False return test failed in app.php on line 2
ini_set('assert.exception', 1);
assert(false, 'Some error message');
// PHP Fatal error: Uncaught AssertionError: Some error message in app.php:2
// Stack trace:
// #0 app.php(2): assert(false, 'Some error mess...')
// #1 {main}
// thrown in app.php on line 2
별도의 에러도 가능하다.
ini_set('assert.exception', 1);
class CustomError extends AssertionError {}
assert(false, new CustomError('some error message'));
// PHP Fatal error: Uncaught CustomError: Some error message in app.php:4
// Stack trace:
// #0 app.php(4): assert(false, 'Some error mess...')
// #1 {main}
// thrown in app.php on line 4
use
선언 모아쓰기
// 이전 방식
use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;
use function some\namespace\fn_a;
use function some\namespace\fn_b;
use function some\namespace\fn_c;
use const some\namespace\ConstA;
use const some\namespace\ConstB;
use const some\namespace\ConstC;
// 새 방식
use some\namespace\{ClassA, ClassB, ClassC as C};
use function some\namespace\{fn_a, fn_b, fn_c};
use const some\namespace\{ConstA, ConstB, ConstC};
제너레이터 반환 표현
$gen = (function() {
yield 1;
yield 2;
return 3;
})();
foreach ($gen as $val) {
echo $val, PHP_EOL;
}
// 1
// 2
echo $gen->getReturn(), PHP_EOL;
// 3
제너레이터의 yield
가 모두 끝난 후 최종적인 반환 값을 받아올 수 있도록 getReturn()
메소드가 추가되었다.
제너레이터 위임
yield from
으로 제너레이터 중간에 커서를 위임할 수 있다.
function gen()
{
yield 1;
yield 2;
yield from gen2();
}
function gen2()
{
yield 3;
yield 4;
}
foreach (gen() as $val)
{
echo $val, PHP_EOL;
}
// 1
// 2
// 3
// 4
intdiv()
정수 나누기 함수
php에서는 정수를 대상으로 /
연산자를 사용하면 소숫점 나누기 결과가 나온다. 정수 나누기에는 새로 소개된 intdiv()
를 사용하면 된다.
$quotient = intdiv(10, 3); // 몫
$remainder = 10 % 3; // 나머지
$div = 10 / 3; // 나누기
echo $quotient; // 3
echo $remainder; // 1
echo $div; // 3.3333333333333
세션 옵션
세션 옵션이 ini에 설정되어 있었는데 이제 session_start()
에서도 지정할 수 있다. 가능한 설정 목록.
session_start([
'cache_limiter' => 'private',
'read_and_close' => true,
]);
preg_replace_callback_array()
추가
정규표현식에 맞는 경우 해당 콜백을 실행한다. 기존 preg_replace_callback()
으로 장황하게 작성한 코드를 개선할 수 있다. 문서.
$subject = 'Aaaaaa Bbb';
preg_replace_callback_array(
[
'~[a]+~i' => function ($match) {
echo strlen($match[0]), ' matches for "a" found', PHP_EOL;
},
'~[b]+~i' => function ($match) {
echo strlen($match[0]), ' matches for "b" found', PHP_EOL;
}
],
$subject
);
// 6 matches for "a" found
// 3 matches for "b" found
CSPRNG 함수 추가
암호학적으로 안전하며 크로스 플랫폼으로 동작하는 함수가 추가되었다.
random_bytes()
random_int()
list()
로 ArrayAccess
구현 객체 풀기 가능
보장되지 않던 부분인데 수정되었다.
클래스 맴버 접근에 clone
가능하도록 추가
(clone $foo)->bar();
변경/추가된 함수 (일부)
dirname()
:depth
2번 파라미터로 몇 단계 위 경로를 반환할 지 지정할 수 있다.exec()
,system()
,passthru()
: NULL 바이트 보호가 추가되었다.substr()
,iconv_substr()
: 해당하는 값이 없을 때 빈 문자열을 반환하도록 변경되었다.error_clear_last()
:error_get_last()
를 비운다.
추가된 클래스/인터페이스 (일부)
- Reflection
- Exception Hierachy
그 외 변경사항
예약어 제한 완화
문맥적인 fluent 인터페이스 개발이 가능하도록 예약어 제한이 약해졌다. (class는 ClassName::class
때문에 여전히 사용할 수 없다.)
// 'new', 'private', 'for' 등 전에 사용하지 못했던 메소드명
Project::new('Project Name')
->private()
->for('purpose here')
->with('username here');
date.timezone 경고 제거
설정되지 않은 경우에 나오던 경고가 제거되었다. 기본값은 UTC다.
PHP 7.1
호환성 문제 있는 변경
적은 수의 매개변수로 함수를 호출하는 경우
사용자 정의 함수를 호출할 때, 필요한 매개변수보다 적은 수의 인자로 호출하면 경고 대신 오류 예외가 발생한다.
function hello($param) {}
hello();
// Uncaught ArgumentCountError: Too few arguments to function hello(), 0 passed in...
스코프 연관 함수의 동적 호출 금지
함수가 다 른 함수의 기능을 들여다 보거나 스코프를 수정하는 경우라면 동적 호출하는 과정에서 의미가 모호하거나 불안정할 수 있다.
- assert() - with a string as the first argument
- compact()
- extract()
- func_get_args()
- func_get_arg()
- func_num_args()
- get_defined_vars()
- mb_parse_str() - with one arg
- parse_str() - with one arg
(function () {
$func = 'func_num_args';
$func();
})();
// Uncaught Error: Cannot call func_num_args() dynamically in....
추가된 금지어
클래스명, 인터페이스나 trait 이름 금지어로 다음 단어가 추가되었다.
- void
- iterable
숫자 문자열 변경이 과학적 표기법을 준수함
(int)
캐스팅, intval()
, settype()
, decbin()
, decoct()
, dechex()
에도 동일하게 적용되었다.
mt_rand()
알고리즘 수정
제대로 된 Mersenne Twister 알고리즘으로 동작한다. 기존 잘못된 구현으로 함수를 실행하려면 MT_RAND_PHP를 두번째 인자로 전달해서 구동할 수 있다.
rand()
는 mt_rand()
, srand()
는 mt_srand()
를 수행
이 변경은 shuffle()
, str_shuffle()
, array_rand()
의 출력에도 영향을 준다.
ASCII 삭제 제어 문자를 식별자로 사용할 수 없음
0x7E
를 더 이상 식별자로 사용할 수 없다.
error_log
가 syslog
로 설정된 경우 syslog
의 오류 레벨 설정을 따름
불완전한 개체에서 소멸자를 호출하지 않음
불완전한 개체에 대해 소멸자를 호출하지 않도록 변경되었다. 즉, 생성자에서 예외가 발생했을 때 그 개체의 소멸자가 호출되지 않는다.
call_user_func()
에 참조 인자 사용
call_user_func()
에 참조 인자를 사용하는 경우 경고가 표시된다. 경고는 표시되지만 호출 자체는 문제 없이 된다.