PHP 부록에 있는 이주 문서를 읽으면서 정리했다. 완전한 번역은 아니며 중요도가 높다고 생각되는 부분을 주로 정리했다. 세세한 부분이나 함수는 각각 문서를 참고하는 것을 권장한다.

목차

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() 함수에서 오류가 발생한 경우 ParseErrorcatch로 잡아서 처리할 수 있게 되었다.

변수 사용 변경점

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()를 비운다.

변경 전체 목록, 추가 전체 목록.

추가된 클래스/인터페이스 (일부)

전체 목록

그 외 변경사항

예약어 제한 완화

문맥적인 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_logsyslog로 설정된 경우 syslog의 오류 레벨 설정을 따름

불완전한 개체에서 소멸자를 호출하지 않음

불완전한 개체에 대해 소멸자를 호출하지 않도록 변경되었다. 즉, 생성자에서 예외가 발생했을 때 그 개체의 소멸자가 호출되지 않는다.

call_user_func()에 참조 인자 사용

call_user_func()에 참조 인자를 사용하는 경우 경고가 표시된다. 경고는 표시되지만 호출 자체는 문제 없이 된다.

문자열에서의 빈 인덱스 연산자 지원 제거

예전에는 $str[] = $x 처럼 작성하면 말 없이 배열로 변환되었지만 이제는 심각한 오류가 발생한다.

문자열에서의 인덱스 연산자

예전에는 말 없이 배열로 변환되었던 동작인데 이제는 문자열 인덱스로 접근해서 값을 배정하는 형식으로 동작한다. 대신에 첫 글자만 사용한다.

$a = '';
$a[10] = 'foo';
var_dump($a);
// Warning:  Only the first byte will be assigned to the string offset in...
// string(11) "          f"

동일 요소에 대한 정렬 순서

정렬할 때 동일한 요소로 판단되면 그 순서가 어느 것이 먼저 온다는 보장이 없다. 그러므로 동일한 요소가 있는 목록의 경우는 그 결과 순서에 의존적인 코드를 작성해서는 안된다.

unserialize() 함수의 $optionsallowed_classes

unserialize() 함수를 호출하면서 $optionsallowed_classesarray|bool 타입이 아니면 false를 반환하고 E_WARNING이 발생한다.

DateTime 생성 시 마이크로초 반영

이전까지 마이크로초가 제대로 반영되지 않아서 다음과 같은 호출이 거의 true 였지만 이제는 false를 반환할 가능성이 높아졌다.

new DateTime() ==  new DateTime();

많은 fatal error가 Error 예외로 전환

전체 목록.

클로저에 use에서 제한된 함수명

클로저에 use 생성자를 사용할 때 슈퍼 전역 변수나 $this 등을 사용하면 에러가 발생한다. 매개변수와 동일한 이름도 오류가 발생한다.

$f = function () use ($_SERVER) {};
// Fatal error:  Cannot use auto-global as lexical variable in...

$f = function () use ($this) {};
// Fatal error:  Cannot use $this as lexical variable in...

$f = function ($param) use ($param) {};
// Fatal error:  Cannot use lexical variable $param as a parameter name in...

JSON 변환

serialize_precision 환경 설정으로 double 인코딩 시 정밀도를 지정할 수 있다.

이전에 빈 키를 _empty_ 프로퍼티로 변환하던 것이 고쳐져서 진짜 빈 키로 지정된다.

var_dump(json_decode(json_encode(['' => 1])));
// object(stdClass)#1 (1) {
//   [""]=>
//   int(1)
// }

반환 타입이 지정된 경우에 return; 금지

반환 타입이 지정된 함수에 return;을 사용하면 설령 코드가 해당 반환문에 절대 도착하지 않더라도 E_COMPILE_ERROR가 발생한다.

function sayHello(): string {
	if (true) {
		return "Hello";
	}
	return;
}
// Fatal error:  A function with return type must return a value in...

새 기능

Nullable 타입

타입 선언에서 반환 값이 타입 개체 또는 null인 경우 ?를 사용해서 nullable 타입으로 선언할 수 있다.

function getNothingWrong(): string
{
  return null;
}

var_dump(getNothingWrong());
// Uncaught TypeError: getNothing(): Return value must be of type string, null returned in...

function getNothing(): ?string
{
  return null;
}

var_dump(getNothing()); // null
function getHello(?string $name): string
{
  return "Hello " . ($name ?? 'stranger') . "!";
}

var_dump(getHello(null)); // "Hello stranger!"
var_dump(getHello('Edward')); // "Hello Edward!"
var_dump(getHello());
// Uncaught ArgumentCountError: Too few arguments to function getHello(), 0 passed in...

Void 함수

반환이 없거나 반환 값이 없는 함수의 경우, 타입 선언에 void를 넣을 수 있다. 해당 함수를 변수에 배당하면 null이 나오며 별도의 오류는 발생하지 않는다.

function sayHello(): void
{
  echo 'Hello!' . PHP_EOL; // 반환이 없음
}

function sayBye(): void
{
  echo 'Bye!' . PHP_EOL;
  return; // 빈 반환
}

$a = sayHello();
var_dump($a); // null

null 반환은 void에 해당하지 않는다. 다음과 같이 오류가 발생한다.

function saySomething(): void
{
  echo 'Something!' . PHP_EOL;
  return null;
}
// Fatal error:  A void function must not return a value
//    (did you mean "return;" instead of "return null;"?) in...

배열 분해

[] 문법으로 간단하게 배열을 분해할 수 있다.

$data = [
  [1, 'Tom'],
  [2, 'Fred'],
];

// list() 사용
//
list($id1, $name1) = $data[0];

foreach($data as list($id, $name)) {
  // $id, $name
}

// [] 사용
//
[$id1, $name1] = $data[0];

foreach($data as [$id, $name]) {
  // $id, $name
}

클래스 상수 접근 제한자

클래스 상수에도 접근 제한자를 설정할 수 있게 변경되었다.

class ConstDemo
{
  const PUBLIC_CONST_A = 1;
  public const PUBLIC_CONST_B = 2;
  protected const PROTECTED_CONST = 3;
  private const PRIVATE_CONST = 4;
}

iterable 임시 타입

반복에 사용할 수 있는 iterable 임시 타입이 추가되었다. Traversable 인터페이스를 구현한 개체나 배열을 모두 받는다. (callable도 임시 타입 중 하나로 호출 가능한 다양한 타입/형태를 받는 것과 유사하다.)

funciton iterator(iterable $iter) {
  // ...
}

여러 예외 catch로 한번에 다루기

파이프 문자(|)로 여러 예외를 한번에 처리할 수 있다.

try {
  // some code
} catch (FirstException | SecondException $e) {
  // 두 예외 모두 처리하기
}

list() 키 지원

배열을 분해할 때 키를 지정할 수 있다. list()[] 모두 지원한다.

$data = [
  ['id' => 1, 'name' => 'Hellen'],
  ['id' => 2, 'name' => 'Jane'],
];

// list() 사용
//
list('id' => $id1, 'name' => $name1) = $data[0];
foreach ($data as list('id' => $id, 'name' => $name)) {
  // $id, $name
}

// [] 사용
//
['id' => $id1, 'name' => $name1] = $data[0];
foreach ($data as ['id' => $id, 'name' => $name]) {
  // $id, $name
}

문자열 음수 오프셋 지원

var_dump("abcdef"[-2]); // e
$str = "Hello";
echo "The last character of '$str' is '$str[-1]'.\n";
// "The last character of 'Hello' is 'o'."

Closure::fromCallable(): callableClosure개체로 변환

이 함수로 callableClosure 개체로 변환할 수 있어 좀 더 일관성을 갖출 수 있다.

class ShySpeaker
{
  public function exposeWhisper()
  {
    return Closure::fromCallable([$this, 'whisper']);
  }

  private function whisper($saying)
  {
    echo "'$saying', the speaker whispered.";
  }
}

$privateFunc = (new ShySpeaker)->exposeWhisper();
$privateFunc('dang na gui gui');
// "'dang na gui gui', the speaker whispered."

비동기 시그널 처리

문서.

pcntl_async_signals(true); // turn on async signals

pcntl_signal(SIGHUP,  function($sig) {
    echo "SIGHUP\n";
});

posix_kill(posix_getpid(), SIGHUP);

추가된 함수 (일부)

  • session_create_id()
  • session_gc()
  • is_iterable()

추가된 함수 전체 목록.

변경된 함수 (일부)

  • getopt(): 3번째 인자를 참조로 넘기면 어디까지 처리했는지 인덱스를 받을 수 있다.
  • getenv(): 인자 없이 호출하면 전체 값을 배열로 반환한다.
  • get_headers(): 스트림 맥락을 보낼 수 있다.
  • parse_url(): 더 제한적으로 동작하고 RFC3986를 지원한다.
  • unpack(): 세번째 인자로 어디서부터 변환이 시작되었는지 지정할 수 있다.
  • session_start(): 세션 시작에 실패하면 false 반환하도록 변경한다.

전제 목록.

그 외

  • 잘못된 문자열로 산술 연산을 수행하면 E_WARNING, E_NOTICE 둘 다 발생한다.
  • null이 허용되는 경우에 TypeError가 해당 부분도 알려준다. (e.g. must be blarblar or null)

PHP 7.2

새 기능

object 타입

공변(covariant) 반환 타입과 반공변(contravariant) 매개변수 타이핑에 사용할 수 있는 새 타입 object가 소개되었다.

function test(object $obj): object
{
  return new SplQueue();
}

test(new StdClass());

dl(): 이름으로 확장 불러오기

dl() 함수로 .so, .dll 확장을 불러올 수 있다.

추상 메소드 오버라이드

확장한 추상 클래스에서 추상 메소드를 오버라이드 할 수 있다.

abstract class A
{
  abstract function test(string $s);
}

abstract class B extends A
{
  // 오버라이드 됨. 대신 매개변수의 반공변성과 반환값의 공변성을 따라야 함.
  abstract function test(string $s): int;
}

class Imp extends B
{
  function test(string $s) : int {
    return 3;
  }
}

Sodium이 코어 확장에 포함

현대적인 암호화 라이브러리 Sodium이 포함되었다. 문서.

매개변수 타입 확장

인터페이스의 메소드를 오버라이드하면서 매개변수 타입 선언을 지우는 것으로 확장할 수 있다. 매개변수가 반공변성을 유지하기 때문에 이 확장은 리스코프 치환 원칙(LSP)를 준수한다.

interface A
{
  public function Test(array $input);
}

class B implements A
{
  public function Test($input){} // 타입을 제거함
}

네임스페이스에 후행 쉼표(trailing comma) 허용

use Hello\World\{
  Foo,
  Bar,
  Baz
};

새로운 함수

문서.

호환성 문제 있는 변경

number_format()에서 -0 반환 문제 수정

따지고 보면 IEEE 754에는 적합한 표현이지만 사람 눈에 이상해보여서 제거되었다.

var_dump(number_format(-0.01)); // 0

개체에서 배열 캐스팅에 숫자 키 변환 수정

// array -> object
//
$arr = [0 => 1];
$obj = (object) $arr;
var_dump(
  $obj,
  $obj->{'0'},
  $obj->{0},
);
// object(stdClass)#1 (1) {
//   ["0"]=> // 문자열 키로 처리됨
//   int(1)
// }
// int(1)
// int(1)

// object -> array
$obj = new class {
  public function __construct()
  {
    $this->{0} = 1;
  }
};
$arr = (array)$obj;

var_dump(
  $arr,
  $arr[0],
  $arr['0'],
);
// array(1) {
//   [0]=>    // 정수 키로 처리됨
//   int(1)
// }
// int(1)
// int(1)

get_class()null 넘기는 기능이 없어짐

null을 넘기면 현재 맥락에 맞는 클래스명을 반환했는데 이제 단순히 인자 없이 호출하면 된다.

count()로 셀 수 없는 타입을 호출한 경우 경고

var_dump(
  count(null), // NULL은 셀 수 없음
  count(1), // 정수는 셀 수 없음
  count('abc'), // 문자열은 셀 수 없음 (대신 `sizeof()`)
  count(new stdclass), // Countable 구현 안하면 셀 수 없음
  count([1,2]), // 배열을 셀 수 있음
);

참고: 이후 버전에서는 TypeError가 발생하며 값을 반환하지 않는다.

__PHP_Incomplete_Classis_object()

이전엔 false였는데 이제는 true 반환한다.

array_unique()

array_unique()SORT_STRING을 사용했을 때 배열을 복사해서 중복을 지우는 방식이었다. 이제는 새로운 배열에 요소를 추가하는 방식으로 변경되었다. 그 결과로 숫자 인덱스의 결괏값이 달라졌다.

PHP 7.3

새 기능

배열 분해에서 참조 할당 지원

$d = ['a', [1, 2]];

// [] 사용
[&$a, [$b, &$c]] = $d;
$a = 'c';
echo $d[0]; // "c"

// list() 사용
list(&$a, list($b, &$c)) = $d;
$c = 3;
echo $d[1][1]; // 3

instanceof 연산자에서 리터럴 처리

instanceof가 리터럴을 받을 수 있도록 변경되었으며 항상 false를 반환한다.

class Hello {}
var_dump('Hello' instanceof Hello); // false
var_dump(new Hello instanceof Hello); // true

CompileError 예외 추가

몇 가지 심각 오류를 만들던 컴파일 오류가 ParseError에서 파생된 CompileError로 변경되었다.

호출에서 후행 쉼표 사용 가능

함수와 메소드 모두에서 후행 쉼표를 사용할 수 있다.

hello(
  $a,
  $b,
  $c,
  $d, // 예전에 오류가 나던 부분
);

mbstring 개선

본문 참조.

새로운 함수 (일부)

  • array_key_first(), array_key_last(): 배열의 처음 혹은 마지막 키를 반환한다.
  • gc_status(): 가비지 컬렉터의 상태를 반환한다.
  • hrtime(): 시스템의 고해상도 시간을 반환한다.
  • is_countable(): Countable 인터페이스를 구현했거나 셀 수 있는 지 확인한다.
  • net_get_interfaces(): 네트워크 인터페이스 정보를 반환한다.
  • DateTime::createFromImmutable: DateTimeImmutable 개체에서 DatTime 개체를 생성한다.

전체 목록.

호환성 문제 있는 변경

Heredoc/Nowdoc 종결 표식 해석 변경

$str = <<<FOO
abcdefg
   FOO  // 종결 표식 앞에 들여쓰기가 있으면 오류가 나도록 변경됨
FOO;

switch 내에서 continue 경고

switch에서 continuebreak와 동일하게 동작하기 때문에 continue 2를 의도하고 쓴 것인지 확인하는 경고가 추가되었다.

while ($foo) {
    switch ($bar) {
      case "baz":
         continue;
         // Warning: "continue" targeting switch is equivalent to
         //          "break". Did you mean to use "continue 2"?
   }
}

정적 프로퍼티 참조 문제 수정

정적 프로퍼티가 공유되어야 하는데 참조 배정을 했을 경우에 공유가 되지 않던 문제가 수정되었다.

class Test {
    public static $x = 0;
}
class Test2 extends Test { }

Test2::$x = &$x;
$x = 1;

var_dump(Test::$x, Test2::$x);
// 이전: int(0), int(1)
// 현재: int(1), int(1)

배열, 프로퍼티 접근자를 참조로 사용하는 경우 바로 값을 반환하도록 변경

$arr = [1];
var_dump($arr[0] + ($arr[0] = 2));
// 이전: int(4)
// 현재: int(3)
$arr = [1];
$ref =& $arr[0];
var_dump($ref + ($arr[0] = 2));
// int(4)

현재는 이렇게 동작하긴 하지만 표현식 하나에서 값을 읽고 쓰는 부분에 대한 정의가 존재하지 않는다. 그래서 미래에 다른 결과가 나올 수도 있으므로 더 명확한 방식으로 구현하기를 권한다.

Traversable의 정수가 아닌 숫자 키로 인자 분해 안되도록 변경

function foo(...$args) {
    var_dump($args);
}
function gen() {
    yield 1.23 => 123;
}
foo(...gen());
// Uncaught Error: Keys must be of type int|string during argument unpacking in...

더 이상 사용되지 않는 기능 (일부)

  • 대소문자 구분 안하는 define(): 3번째 인자로 대소문자 구분 안하도록 설정 가능했으나 이제 deprecated 되었다.
  • 네임스페이스 내 assert()

전체 목록.

그 외 변경점 (일부)

var_export()는 이제 더 이상 존재하지 않는 stdClass::__setState() 메소드 대신 (object)로 캐스팅 하는 방식으로 변경되었다.

array_push()arry_unshift()는 단일 인자로도 호출 가능하도록 변경되었다. ... 연산자와 함께 사용하는데 편리하도록 개선된 부분이다.

$items = [1, 2, 3];

$pushPayload = [&$items, 4];
array_push(...$pushPayload);

var_dump($items);
// array(4) {
//   [0]=>
//   int(1)
//   [1]=>
//   int(2)
//   [2]=>
//   int(3)
//   [3]=>
//   int(4)
// }

전체 목록.

PHP 7.4

새 기능

클래스 타입 프로퍼티

클래스 프로퍼티에도 타입을 지정할 수 있게 되었다. 타입이 맞지 않는 값을 넣으면 TypeError가 발생한다.

class user {
  public int $id;
  public string $name;
}

$a = new User();
$a->id = 'test';
// Uncaught TypeError: Cannot assign string to property User::$id of type int in...

화살표 함수

화살표 함수는 암묵적 값 스코프(implicit by-value scope)를 지원하는 축약 문법이다.

$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3, 4]);
// [10, 20, 30, 40]

제한적인 공변 반환 타입과 반공변 인자 타입

class A {}
class B extends A {}

class Producer {
  public function method(): A {}
}
class ChildProducer extends Producer {
  public function method(): B {}
}

오토로딩이 사용되는 경우에는 모든 변성을 지원한다. 단일 파일에서 작성하는 경우에는 순환 참조를 하지 않는 경우에만 사용할 수 있다. 참조 전에 미리 선언되어 있어야 하기 때문이다.

Null 병합 배정 연산자

$array['key'] ??= computeDefault();
// 다음과 유사
if (!isset($array['key'])) {
  $array['key'] = computeDefault();
}

배열 내 배열 풀기

$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon']

숫자 리터럴 구분자

6.674_083e-11; // float
299_792_458;   // decimal
0xCAFE_F00D;   // hexadecimal
0b0101_1111;   // binary

약한 참조

약한 참조는 프로그래머가 개체가 파괴되더라고 개체에 대한 참조를 유지할 수 있는 기능을 제공한다. WeakReference 클래스를 활용한다.

// 약한 참조
$obj = new stdClass;
$weakref = WeakReference::create($obj);
var_dump($weakref->get());
// object(stdClass)#1 (0) {
// }

unset($obj);
var_dump($weakref->get());
// NULL
// 참조
$obj = new stdClass;
$ref = & $obj;
var_dump($ref);
// object(stdClass)#1 (0) {
// }

unset($obj);
var_dump($ref);
// object(stdClass)#1 (0) {
// }
// 참조로 인해 개체가 파괴되지 않음

__toString()에서 예외 허용

이전에는 예외가 있는 경우에 심각 오류가 발생했는데 이제 예외를 던질 수 있게 변경되었다.

mb_str_split() 추가

str_split()과 동일한 역할을 하는 멀티바이트 함수가 추가되었다.

strip_tags()에 태그명 배열 사용

strip_tags($str, '<a><p>');
strip_tags($str, ['a', 'p']); // 동일한 기능

커스텀 개체 직렬화

다음 두 매직 메소드로 개체의 직렬화와 역직렬화를 제어할 수 있다.

// 반환 배열은 개체에 필요한 모든 상태를 포함
public function __serialize(): array;

// 개체 상태를 제시된 자료 배열을 사용해서 복구함
public function __unserialize(array $data): void;

array_merge(), array_merge_recursive() 단일 인자로도 호출 가능하도록 변경

array_push()와 동일한 이유에서 변경되었다.

array_merge(...$arrays);

proc_open() 배열 지원

proc_open(['php', '-r', 'echo "Hello World\n";'], $descriptors, $pipes);

// `redirect`와 `null` 디스크립터도 지원
proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['null']], $pipes);

새로운 클래스/함수 (일부)

전체 목록.

호환성 문제 있는 변경

배열이 아닌 값에 배열 스타일로 접근하면 알림 발생

null, bool, int, float, 또는 어떤 리소스든 배열이 아닌 것에 배열처럼 접근하면 E_WARNING이 발생한다.

$a = 3;
$a[0];
// Warning: Trying to access array offset on value of type int in...

get_declared_classes()와 익명 클래스

더 이상 초기화 되지 않은 익명 클래스는 get_declared_classes() 함수에서 반환하지 않는다.

fn 키워드 예약어 지정

화살표 함수로 추가된 이 키워드는 더 이상 함수나 클래스명으로 사용할 수 없다. 메소드나 클래스 상수로는 여전히 사용할 수 있다.

파일 끝에 <?php 오류 수정

파일 끝에 <?php가 있으면 문법 오류가 나던 부분이 수정되었다.

비밀번호 알고리즘 상수 변경

이전에는 알고리즘 상수가 정수였는데 ?string으로 변경되었다.

  • PASSWORD_DEFAULT: int 1 -> string '2y' (PHP 7.4.0, 7.4.1, 7.4.2에서는 null)
  • PASSWORD_BCRYPT: int 1 -> string '2y'
  • PASSWORD_ARGON2I: int 2 -> string 'argon2i'
  • PASSWORD_ARGON2ID: int 3 -> string 'argon2id'

htmlentities()

지원이 부족한 인코딩으로 된 데이터를 처리하는 경우에 알림이 발생한다.

일자와 시간

DateInterval 개체를 비교하면 항상 false를 반환한다.

Reflection 개체의 직렬화 불가

원래 지원하지 않던 부분인데 명시적으로 금지되었다.

더 이상 사용되지 않는 기능 (일부)

명시적이지 않는 중첩 삼항 연산자

이후 버전에서는 아예 제거되어서 Fatal error가 발생한다.

1 ? 2 : 3 ? 4 : 5;   // deprecated
(1 ? 2 : 3) ? 4 : 5; // ok
1 ? 2 : (3 ? 4 : 5); // ok

// PHP 8+
// Fatal error:  Unparenthesized `a ? b : c ? d : e` is not supported.
//     Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` in...

중간에 있는 경우는 모호하지 않아서 중첩에도 여전히 동작한다.

1 ? 2 ? 3 : 4 : 5; // ok

배열, 문자열 오프셋 접근에 {} 사용 중단

// $var{$idx}; 대신 아래 방식 사용
$var[$idx];

(real)is_real() 중단

대신 (float)is_float()을 사용하기를 권장한다.

부모 클래스가 없는데 parent 키워드 사용 중단

중단 메시지가 나왔으나 PHP 8+ 에서는 심각 오류가 발생한다.

class Hello {
	public function __construct() {
		parent::__construct();
	}
}
// Fatal error:  Cannot use "parent" when current class scope has no parent in...

array_key_exists()를 개체에 사용하기 중단

대신 isset() 또는 property_exists()를 사용한다.

money_format() 함수 중단

대신 NumerFormatter 기능을 사용한다.

리플렉션 관련

ReflectionType::__toString() 중단

대신 ReflectionNamedType::getName()을 사용한다.

ReflectionClass::export() 메소드 중단

대신 개체가 문자열로 변환된다.

// ReflectionClass::export(Foo::class, false)는 다음에 대응
echo new ReflectionClass(Foo::class), "\n";

// $str = ReflectionClass::export(Foo::class, true)는 다음에 대응
$str = (string) new ReflectionClass(Foo::class);

그 외

문서 참고.

PHP 8.0

새 기능

새 기능 전체 목록.

명명된 인수(Named arguments) 추가

myFunction(paramName: $value);
array_foobar(array: $value);


function person($name, $age) {
	echo "name: $name, age: $age" . PHP_EOL;
}

// 순서가 바뀌어도 됨
person(age: 72, name: 'Sejong');
// 순서 인자와 명명 인자를 섞어도 됨
person('Sejong', age: 72);

// 인수명을 변수로 넣는 것은 지원하지 않음
function_name($variableStoringParamName: $value);

// 동일한 명명 인자를 쓰면 가장 마지막 인자가 덮어씀
person(name: 'Hana', name: 'Narae'); // $name = 'Narae'
person('Hana', name: 'Narae'); // $name = 'Narae'

어트리뷰트(Attribute) 추가

코드를 선언할 때 기계가 분석할 수 있는 메타 정보를 추가할 수 있도록 어트리뷰트를 지원한다. 이 어트리뷰트의 정보는 리플렉션 API를 통해서 접근할 수 있다.

#[Attribute]
class Setup {}

class CopyFile {
  // ...
  #[SetUp]
  public function fileExists() {
    // ...
  }
}

자세한 내용은 문서를 확인한다.

// 사용 예시
#[MyAttribute]
#[\MyExample\MyAttribute]
#[MyAttribute(1234)]
#[MyAttribute(value: 1234)]
#[MyAttribute(MyAttribute::VALUE)]
#[MyAttribute(array("key" => "value"))]
#[MyAttribute(100 + 200)]
class Thing
{
}

#[MyAttribute(1234), MyAttribute(5678)]
class AnotherThing
{
}

클래스 생성자 시그니처로 프로퍼티 선언하기 (constructor property promotion)

생성자 시그니처에 접근 제한자를 포함하면 해당 시그니처를 사용해서 프로퍼티를 자동 할당해준다. 프로모션은 순서에 영향받지 않는다.

class Point {
	public function __construct(protected int $x, protected int $y = 0) {
	}

	public function print(): void {
		echo "x: $this->x, y: $this->y" . PHP_EOL;
	}
}

$p = new Point(1, 2);
$p->print();

유니언 타입 (Union type) 추가

|를 사용해서 타입을 합집합으로 사용할 수 있다. T1|T2|... 방식으로 사용한다.

유니언 타입을 선언할 때 null 유니언 타입도 지원한다. 즉 T1|T2|null 식으로 nullable 합집합을 만들 수 있다. (null은 단독으로 쓸 수 있는 타입은 아니며 여기서만 특수하게 사용 가능하다.)

유니언 타입을 선언할 때 false 임시 타입도 지원한다. true는 존재하지 않는다. false|null, ?false처럼 사용할 수 없다.

match 표현식 추가

switch와 유사하나 switch===으로 검사하는데 반해 match==으로 검사한다. 문서.

$food = 'cake';

$return_value = match ($food) {
  'apple' => 'This food is an apple',
  'bar' => 'This food is a bar',
  'cake' => 'This food is a cake',
};

var_dump($return_value);
// "This food is a cake"

// 조건 표현에는 값, 변수, 값을 반환하는 함수 등 모두 가능하다.
$expressionResult = match ($condition) {
    1, 2 => foo(),
    3, 4 => bar(),
    default => baz(),
};

?-> null 안전 연산자 추가

null에 안전하게 프로퍼티와 메소드에 접근할 수 있도록 ?-> 연산자가 추가되었다.

$result = $repository?->getUser(5)?->name;

// 다음과 동일
if (is_null($repository)) {
  $result = null;
} else {
  $user = $repository->getUser(5);
  if (is_null($user)) {
    $result = null;
  } else {
    $result = $user->name;
  }
}

WeakMap 클래스 추가

레퍼런스 카운트에 영향을 주지 않고 개체를 키로 사용할 수 있는 WeakMap 클래스가 추가되었다. 문서.

$wm = new WeakMap();

$o = new StdClass;

class A {
    public function __destruct() {
        echo "Dead!\n";
    }
}

$wm[$o] = new A;

var_dump(count($wm));
echo "Unsetting...\n";
unset($o);
echo "Done\n";
var_dump(count($wm));

ValueError 클래스 추가

인자 타입에 맞게 값이 전달되었지만 값이 맞는 범위에 들지 않는 경우에 사용할 수 있는 ValueError 클래스가 추가되었다.

메소드 시그니처에 가변 인자 사용 가능

타입이 호환되는 상황이라면 다음처럼 가변 인자를 사용할 수 있다.

class A {
  public function method(int $many, string $parameters, $here) {}
}
class B extends A {
  public function method(...$everything) {}
}

지연된 바인딩을 위한 static 반환 지원

self와 비교해볼 수 있는 키워드로 아래 예제를 참고한다.

class Test {
	public function createSelf(): self {
		return new self();
	}
	public function createStatic(): static {
		return new static();
	}
}

class WorkTest extends Test {
}

$test = new Test;
$self = $test->createSelf();
$static = $test->createStatic();

var_dump($self);
var_dump($static);
// object(Test)#2 (0) {
// }
// object(Test)#3 (0) {
// }

$workTest = new WorkTest;
$workSelf = $workTest->createSelf();
$workStatic = $workTest->createStatic();

var_dump($workSelf);
var_dump($workStatic);
// object(Test)#5 (0) {
// }
// object(WorkTest)#6 (0) {
// }

Stringable 인터페이스 추가

Stringable 인터페이스가 추가되었다. __toString() 메소드가 구현되어 있으면 자동으로 구현된 것으로 처리된다.

class Hello {
	public function __toString() {
		return "Hello";
	}
}

function hey(Stringable $s) {
	echo "YES";
}

hey(new Hello);

throw를 표현식에 사용

throw를 표현식에도 사용할 수 있다.

$fn = fn() => throw new Exception('Exception in arrow function');
$user = $session->user ?? throw new Exception('Must have user');

그 외 추가된 부분

  • 개체에서도 클래스명을 찾을 수 있다. $object::classget_class($object)와 동일한 결과를 반환한다.
  • newinstanceof를 표현식과 함께 사용할 수 있다. 예: new (expression)(...$args), $obj instanceof (expression).
  • Trait으로 추상 private 메소드도 정의할 수 있다.
  • catch (Exception)으로 변수를 저장하지 않고도 try catch 할 수 있다.
  • mixed 타입이 추가되었다. object|resource|array|string|int|float|bool|null와 동일하다.
  • str_contains(), str_starts_with(), str_ends_with()가 추가되었다.
  • array_diff(), array_intersect()도 첫 인자만 갖고도 실행할 수 있다.

호환성 문제 있는 변경

변경점이 많은데 중요하다고 생각되는 것만 아래 정리했다. 전체 목록을 확인하자.

문자열-숫자 비교

숫자와 문자열 비교가 여전히 가능하긴 하지만 조금 달라졌다. 이 변경으로 0 == "숫자가 아닌 것"false로 볼 수 있다.

0 == "0"      // true => true
0 == "0.0"    // true => true
0 == "foo"    // true => false
0 == ""       // true => false
42 == " 42"   // true => true
42 == "42foo" // true => false

매직 메소드 반환 타입 지정

다음과 같은 시그니처가 필요하다.

__call(string $name, array $arguments): mixed
__callStatic(string $name, array $arguments): mixed
__clone(): void
__debugInfo(): ?array
__get(string $name): mixed
__invoke(mixed $arguments): mixed
__isset(string $name): bool
__serialize(): array
__set(string $name, mixed $value): void
__set_state(array $properties): object
__sleep(): array
__unserialize(array $data): void
__unset(string $name): void
__wakeup(): void

중복된 메소드를 갖고 있는 trait

중복된 메소드를 갖고 있는 trait은 심각한 오류가 발생한다. 그 전에는 암묵적으로 처리되었다. 이제는 명시적으로 충돌을 해소해야 한다.

trait Hello {
	public function test() {
		echo "Hello test" . PHP_EOL;
	}
}

trait Bye {
	public function test() {
		echo "Bye test" . PHP_EOL;
	}
}

class Person {
	use Hello, Bye {
		Hello::test insteadof Bye; // 명시적으로 어느 클래스를 사용할지 지정
    // Hello::test as protected; // 또는 접근 제한을 변경
		Hello::test as helloTest; // 또는 alias를 지정
    Bye::test as byeTest;
		// Bye::test as private byeTest; // 또는 접근 제한을 변경하며 alias를 지정
	}
}

$a = new Person;
$a->test(); // "Hello test"
$a->helloTest(); // "Hello test"
$a->byeTest(); // "Bye test"

그 외

  • #[는 더 이상 주석으로 처리되지 않고 어트리뷰트에 사용된다.
  • 메소드 시그니처 불일치로 인한 상속 오류는 심각한 오류로 발생한다. 이전엔 경고만 발생했었다.
  • array_key_exists()가 삭제되었다.
  • 함수에 명시적으로 다른 타입이 적혀 있으나 null 타입을 받는다고 선언되지 않은 경우에는 null을 받지 않는다. 이전엔 암묵적으로 null을 받을 수 있었다.
  • 익명 클래스의 이름은 이제 상속 클래스나 구현 인터페이스의 첫번째 이름을 기준으로 생성된다.
  • 개체 생성자에서 exit()이 호출되어도 소멸자는 호출되지 않는다.
  • {}로 개체 오프셋 접근이 없어졌다. []를 사용한다.
  • assert()는 더 이상 문자열을 평가하지 않는다.

그 외에도 변경된 부분이 많다. 전체 목록을 참조한다.

더 이상 사용되지 않는 기능 (일부)

필수 파라미터 앞에 선언된 기본값은 의미 없어짐

아래 코드에서는 $b가 필수로 필요하기 때문에 $a의 기본값이 의미가 없다. 다만 nullable의 경우는 예외적이다.

function test($a = [], $b) {} // 이전
function test($a, $b) {}      // 이후

function test(A $a = null, $b) {} // 아직 가능함
function test(?A $a, $b) {}       // 추천 방식

usort에서 우주선 연산자 사용 권장

대부분 정렬 함수가 boolean으로 처리했으나 이제는 경고를 표시한다. <=>를 권장한다.

// 다음 코드를
usort($array, fn($a, $b) => $a > $b);
// 다음처럼 사용
usort($array, fn($a, $b) => $a <=> $b);

이외 변경 사항

문서 참조.

PHP 8.1

새 기능

문자열 키와 함께 배열 분해하기

$arr1 = [1, 'a' => 'b'];
$arr2 = [...$arr1, 'c' => 'd']; // [1, 'a' => 'b', 'c' => 'd']

인자 분해 후 명명된 인자 지정하기

foo(...$args, named: $arg);

Enumerations 추가

열거형 상수인 enum이 추가되었다. 자세한 내용은 문서를 참고한다.

enum Suit
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

Fibers 추가

코드의 실행 흐름을 제어할 수 있는 Fiber 클래스가 추가되었다. 제너레이터는 스택이 없는 반면에 Fiber는 자체 호출 스택을 갖고 있으며 함수 호출이 복잡하고 중첩되더라도 문제 없이 처리한다. 특히 반환 타입이 제한되어 있는 제너레이터와 다르게 어떤 값이든 반환할 수 있다.

$fiber = new Fiber(function (): void {
   $value = Fiber::suspend('fiber');
   echo "다시 시작된 fiber에서 반환된 값: ", $value, PHP_EOL;
});

$value = $fiber->start();

echo "중단된 fiber에서 반환된 값: ", $value, PHP_EOL;

$fiber->resume('test');

// 중단된 fiber에서 반환된 값: fiber
// 다시 시작된 fiber에서 반환된 값: test

myFunc(...) callable 문법 추가

myFunc(...)Closure::fromCallable('myFunc')와 동일하다. ...은 이 문법의 일부이지 줄임표를 쓴 것이 아니다. (처음엔 이상하게 보이더라도 문자열로 된 callable을 사용하던 걸 생각하면 훨씬 나은 접근이다.)

$a = [1, 2, 3, 4];
function filter_odd($number) {
  return $number % 2 === 0;
}

$r = array_filter($a, filter_odd(...));
var_dump($r);
// [2, 4]

교집합(intersection) 타입 추가

&를 사용해 교집합 타입을 만들 수 있다. T1&T2&.... 합집합 타입과 함께 사용할 수 없다.

Never 타입

새로운 반환 타입으로 never가 추가되었다. 이 타입은 exit()이 실행되거나, 예외가 발생하거나, 아니면 종료되지 않아야 한다.

function done(): never {}
done();
// Uncaught TypeError: done(): never-returning function must not implicitly return in...

function done(): never {
  return;
}
done();
// Fatal error: A never-returning function must not return in...

function done(): never {
  exit;
}
done();

new ClassName() 허용 추가

파라미터 기본값, 정적 변수, 전역 상수 초기화, 어트리뷰트 인자 등에 new ClassName()을 사용할 수 있다.

class School {
	public function __construct(protected string $name) {}
}

class HogwartsStudent {
	public function __construct(
		public string $name,
		public School $school = new School('Hogwarts'),
	) {}
}

$a = new HogwartsStudent(name: 'Harry');
var_dump($a);
// object(HogwartsStudent)#1 (2) {
//   ["name"]=>
//   string(5) "Harry"
//   ["school"]=>
//   object(School)#2 (1) {
//     ["name":protected]=>
//     string(8) "Hogwarts"
//   }
// }

이렇게 작성하면 오류가 발생한다.

class HogwartsStudent {
	public School $school = new School('Hogwarts');

	public function __construct(
		public string $name,
	) {}
}
// Fatal error:  New expressions are not supported in this context in...

readonly 프로퍼티 추가

개체 초기화에만 작성할 수 있는 readonly 프로퍼티가 추가되었다. 타입이 지정된 프로퍼티에만 사용할 수 있다. 정적 클래스에서는 지원하지 않는다.

class Test {
   public readonly string $prop;

   public function __construct(string $prop) {
       // 초기화
       $this->prop = $prop;
   }
}

$test = new Test("foobar");
var_dump($test->prop); // string(6) "foobar"

// 어떤 값이든 다시 배정할 수 없다.
$test->prop = "foobar";
// Error: Cannot modify readonly property Test::$prop
class School {
	public function __construct(public readonly string $name) {}
}

$a = new School('MySchool');
var_dump($a);
// object(School)#1 (1) {
//   ["name"]=>
//   string(8) "MySchool"
// }

$a->name = 'MySchool';
// Uncaught Error: Cannot modify readonly property School::$name in...

final 클래스 상수 지원

class Foo
{
    final public const X = "foo";
}

class Bar extends Foo
{
    public const X = "bar";
}
// Fatal error: Bar::X cannot override final constant Foo::X

새로운 함수 (일부)

  • array_is_list(): 배열이 리스트인지 검증하는 함수다. (associative 배열도 존재하기 떄문에)

전체 목록.

호환성 문제 있는 변경

전체 목록.

$GLOBALS 접근 제한

배열 내에 있는 값에 대한 읽기/쓰기도 되고 $GLOBALS를 읽는 것도 가능하지만 이 배열에 직접 요소를 추가하는 것은 더 이상 불가능하다.

$GLOBALS['hello'] = 'hey';
echo $GLOBALS['hello']; // "hey"

$GLOBALS[] = 'hello';
// Fatal error: Cannot append to $GLOBALS in...

상속된 메소드에서의 static 변수 사용

static 변수로 선언하면 상속 트리 내에서 해당 변수가 공유된다.

class StaticCounter {
	public static function count() {
		static $Staticcounter = 0;
		$Staticcounter++;
		return $Staticcounter;
	}
}

class SomeStaticCounter extends StaticCounter {}

var_dump(StaticCounter::count()); // int(1)
var_dump(StaticCounter::count()); // int(2)
var_dump(SomeStaticCounter::count()); // int(3)
var_dump(SomeStaticCounter::count()); // int(4)
var_dump(StaticCounter::count()); // int(5)
class Counter {
	public function count() {
		static $counter = 0;
		$counter++;
		return $counter;
	}
}

class SomeCounter extends Counter {}

$a = new Counter;
$b = new SomeCounter;
var_dump($a->count()); // int(1)
var_dump($a->count()); // int(2)
var_dump($b->count()); // int(3)
var_dump($b->count()); // int(4)
var_dump($a->count()); // int(5)

필수 파라미터를 옵션 파라미터 뒤에 선언

다음처럼 필수 파라미터를 뒤에 선언한 경우 안내가 나온다.

function makeyogurt($container = "bowl", $flavour)
{
  return "Making a $container of $flavour yogurt.\n";
}
// Deprecated: Optional parameter $container declared before
//     required parameter $flavour is implicitly treated as
//     a required parameter in...

이런 함수를 호출하면 ArgumentCountError 예외가 발생한다.

더 이상 사용되지 않는 기능 (일부)

전체 목록 보기.

__serialize(), __unserialize() 없는 Serializable 인터페이스 구현 중단

이전 버전의 PHP만 지원하기 위한 경우를 제외하고는 모두 구현해야 한다.

null을 받을 수 없는 내장 함수에 null 사용 중단

내장 함수의 스카라 타입은 기본적으로 null을 받을 수 있도록 구현되어 있지만 명시적으로 지정되지 않는 이상 중단한다.

var_dump(str_contains("foobar", null));
// Deprecated: Passing null to parameter #2 ($needle) of type string
//             is deprecated

float 배열 키에 대한 묵시적 int 변환 중단

$a = [];
$a[15] = 'a';
echo $a[15.5]; // deprecated, 묵시적 int 변환으로 0.5 잃고 15를 찾긴 함.
// 'a'
echo $a[15.0]; // ok, as 15.0 == 15
// 'a'

void 함수의 참조 반환 중단

function &test(): void {}
// Deprecated: Returning by reference from a void function is deprecated in...

false의 오토비비피케이션(Autovivification) 중단

오토비비피케이션은 배열로 알아서 생성해주는 기능인데 false는 더 이상 되지 않을 예정이다.

$arr = false;
$arr[] = 2;
// Deprecated: Automatic conversion of false to array is deprecated in...

// Undefined나 null에 대해서는 여전히 가능
$arr2[] = 'some value';
$arr2['doesNotExist'][] = 3;

$arr3 = null;
$arr3[] = 2;

온라인 자료

Introduction to Programming and and Computational Problem-Solving

무료로 제공되는 Java 초급 강의로 타입부터 제네릭, 간단한 자료구조까지 다룬다. 늘 대부분의 강의가 콘솔부터 가르치는 것과 다르게 클래스를 정의하고 내부를 살펴보는 방식이 새롭다. 설명의 깊이는 아쉽지만 찾아볼 수 있는 대부분의 키워드는 제공하고 있다. 더불어 테스트 개념도 같이 얘기해주고 있어서 유익함. 특히 상속과 제네릭을 다루는 부분은 여태 봐왔던 동물-코끼리 같은 상속보다 훨씬 와닿고 실무에 가까운 예제로 설명한다.

How to Design Classes

How to design class: object-oriented programming and computing. Matthias Felleisen, Matthew Flatt, Robert Bruce Findler, Kathryn E. Gray, Shriram Krishnamurthi, Viera K. Proulx

제목처럼 클래스, 인터페이스, 상속, 제네릭 등을 어떻게 활용하는지 예제 중심으로 다루는 책. 디자인 패턴을 다루는 책들에 비해 각각의 맥락을 좀 더 이해하기 편하도록 설명한다.

집에 산다는 것은 걱정 없이 거주할 수 있는 공간이 있다는 안정감과 동시에 어디선가 말없이 자라나는 잡초를 제거해야 한다는 의무가 생긴다. 어느 잡초나 그렇듯 뽑고 돌아서면 다시 솟아나는데 비도 잘 오지 않아 건조한 이 동네서도 잡초가 순식간에 자라난다. 더군다나 잡초들이 습기를 찾아 길게 뿌리를 뻗는 탓에 쉽게 잘 제거도 되지 않아서 계속 골치였다. 뒷마당은 길에서 보이지 않으니까 그렇다 쳐도 앞마당은 늘 오가면서 잡초가 빠르게 자라는 게 보여서 집에 들어오기 전부터 일거리의 압박을 받는 기분이었다. 그래서 텍스 리턴을 조금 받은 것으로 차라리 조경을 맡겨서 해결하자 결론에 닿았고 앞마당 조경을 문의했다. 그렇게 눈엣가시였던 앞마당 조경이 일주일도 안 되는 기간에 정리되었다.

깨끗한 앞마당을 갖게 된 이후로 마음의 평안을 찾을 수 있었지만, 뒷마당은 여전히 난장판이었다. 절반은 민들레 같은 잡다한 풀이고 나머지는 씨가 날려서 이곳저곳 마구 자라난 부추였다. 부추는 가끔 먹기도 해서 살림에 도움이 될 때도 있지만 전혀 관리가 되는 상태가 아니었다. 내가 심지 않는 것은 먹을 수 있어도 잡초다. 우리는 직접 뒷마당을 다 정리하고 돌을 깔아서 잡초 없는 뒷마당을 만들 계획을 세웠다. 앞마당 정리 과정에서 보고 배운 것도 있고 유튜브도 몇 편 봤다고 무엇을 어떻게 할 지 감을 잡았다고 생각했다. (물론 착각이었음.) 어찌 되었든 일단 뒷마당 잡초부터 정리하기 시작했다.

계획

필요/사용했던 도구:

  • 손수레 (wheelbarrow)
  • 삽 (둥근 것 digging, 각진 것 spade)
  • 갈퀴 (rake)
  • 예초기 (트리머, String timmer)
  • 경운기 (틸러, tiller)
  • 캔버스 천 또는 방수포 (자갈 받을 때 바닥에 까는 용도)

중간에 바뀐 계획도 있지만 전체적으로는 계획과 크게 다르지 않게 진행됐다. 작업 과정에서 배수가 제대로 되지 않는 상황인 것을 알게 돼서 기왕 자갈을 사용하는 김에 타공 배수관으로 설치하기로 계획했다.

  1. 잡초 제거 및 땅 정리
    • 예초기로 지표면에 있는 잡초를 제거
    • 경운기로 3 인치 깊이의 흙을 뒤집어 지표면 밑 잡초 뿌리 제거
    • 갈퀴, 삽 등 사용해서 잡초 뿌리 등 모아서 버림
    • 기존에 설치한 급수 시설 정리하기 (1/2' cap 구입해서 막음)
  2. 배수관 설치: 기존 배수관을 제거하고 타공 배수관(프렌치 드레인, french drainage)을 설치
    • 기존 배관 제거
    • 타공 배수관 규격에 맞게 땅 파기
    • 배수관용 부직포(filter fabric) 설치
    • 타공관(corrugated pipe)와 배수구(catch basin) 설치
    • 자갈 채우기
    • 부직포를 감싸고 조경용 핀(staple) 사용해서 고정하기
  3. 땅 준비하기
    • 잡초 약(weed preventer)과 진딧물, 개미 문제로 인한 벌레약 처리
    • 배수관을 통해 물이 잘 흘러가도록 땅 정지 작업 (leveling)
    • 조경용 천(landscape fabric) 깔고 핀으로 고정하기
  4. 조경 자갈 주문 및 깔기
    • 땅 넓이 측정해서 얼마나 주문해야 하는지 계산
    • 인근 조경원 방문해서 설치할 자갈 고르고 주문
    • 약속된 날, 집 앞에 쏟아놓은 돌을 부지런히 나르기
  5. 화분 정리
    • 웃자란 화분은 나눠서 심고 부족한 흙 보충하기
    • 화분 위치를 배수구가 있는 쪽으로 옮겨 깔끔하게 정리
  6. 조명 설치

대부분의 재료, 공구는 홈디포에서 구입했다. 조경용 핀은 아마존에서, 배수관용 부직포는 Landscape Discount에서 구입했다. 조경 자갈은 Sunshine Growers Nursery에서 배달 받았다. 홈디포에서도 자갈을 판매하기는 하지만 대량으로 구매할 때는 로컬에서 구입하는게 훨씬 저렴했다.

계산

가장 중요한 게 절대 부족하거나 남지 말아야 한다는 점이었는데 다행스럽게 조금 남는 선에서 마무리되었다. 용적 계산이 번거로웠던 것은 인치, 피트, 야드 변환이 계속 되었던 부분이다. 특히 미터로 살아온 사람이라서 미터 단위 용적이라면 대충 감이라도 올텐데 매번 변환해서 생각해야 하는 부분이 쉽지 않았다.

  • 조경 자갈 (땅 넓이 x 두께, 자갈 같은 경우는 판매처에 문의하면 알아서 잘 계산해서 얼마나 필요한지 알려줌)
  • 조경 천 (땅 넓이, 곂치는 부분 염두)
  • 배수로 자갈 (길이 x 폭 x 깊이 - 배수관 길이 x 단면 넓이)
  • 배수로 천

천은 겹치는 부분을 사전에 잘 계산하면 설치할 때 편하다. 특히 중간에 나무가 있거나 하면 나무에 맞게 잘라줘야 하는 부분도 있는데 여유분이 있으면 유용하다.

비용

최종적으로 $2200 정도 사용했다. 사용한 장비는 이미 집에 있었던 것들이라서 비용 대부분이 재료 및 자재에 사용되었다.

  • 조경석: $1379 (리버락 $932, 라바락 $447 5 cu. yd., 배달비 포함)
  • 배수로 관련: $400 가량 (배수관 100ft $109, 부직포 $150 등)
  • 조경 관련: $400 가량 (조경 천 4ft x 300ft $87, 조명, 잡초 약 등)

기간

총 기간은 34일(3월 14일부터 4월 17일까지) 정도 된다. 체력적 문제😅로 중간에 쉰 날도 많기 때문에 실제 일한 시간만 따지면 3주 정도 되는 것 같다.

고려사항

  • 체력을 맹신하거나 과신하지 말자. 생각보다 힘들다.
  • 일기예보를 잘 보고 계획을 세울 것. 작업하던 기간에 엄청 더운 주간이 있어서 더 쉽지 않았다.
  • 흙을 손으로 정지 작업 하는 일은 쉽지 않다.
    • 시간을 더 많이 배정하거나 좋은 레벨링 도구를 쓰면 좀 도움 된다.
    • 사실 이것만 전문적으로 하는 장비가 있다. 내 생각엔 30 ft 정도는 손으로 해도 할 만한 수준이라고 생각하고 그 이상이면 전문가에게...
  • 대부분 방법은 유튜브에 답이 많이 있는데 사람마다 말이 다른 부분도 있다. 예를 들면 french drain 찾아보면 사람마다 말이 다른데 자신이 사는 지역과 작업하는 공간이 유사한지 비교해보면 도움이 된다.

과정

잡초 제거로 프로젝트가 시작되었다!

잡초 제거 후 모습. 이후 틸러로 땅을 뒤집었다.

경계석도 꼼꼼하게 닦아준다. 한 톨의 흙이라도 있으면 잡초가 다시 생긴다고.

처음 배송받은 5 cu. yd.의 Arizona River Rock. 배수로와 일부 구역 조경에 사용했다.

기존 배수관을 제거하고 배수로를 정비했다.

배수로에 천을 설치한 후 자갈을 놓고 천을 김밥처럼 말아서 핀을 꽂았다.

두 번째 배달. Lava rock도 5 cu. yd.를 주문했는데 리버락보다 훨씬 적게 느껴졌고 나르기도 수월했다.

조경 천을 놓은 후에 돌을 깔기 시작.

돌도 정돈하고 부직포도 길에 맞게 잘라서 정리

처마 밑에 있던 화분도 한 쪽으로 모았다.

태양광으로 충전되는 조명을 설치했다. 구멍난 잔디에는 씨를 잔뜩 뿌렸다.

결과가 좋으면 고생도 아름답게 느껴진다

정리된 후에는 뒷마당에 나와서 저녁 먹기도 하고 차도 마시고 걷기도 하면서 공간을 더 자주 활용하고 있다. 아직도 작업 후유증으로 손을 매일 주물러야 하는 상황이긴 하지만 깔끔하게 정리된 모습을 보면 정말 뿌듯하다. 편입 결과를 기다리면서 계속 정처 없이 웹페이지를 떠돌던 시간이 연속되고 있었는데 땅을 파면서 심적 안정감을 많이 되찾은 기분이다. 쉽지 않은 과정이었지만 조경 내내 함께한 가족들에게도 고맙다.

많이 배웠고 보람찬 경험이었다고 겁 없이 적으면 또 이런 일을 하게 될까 걱정이 앞선다. 이런 조경을 또 할 일이 있을까 싶긴 하지만. 앞으로 몇 년은 화분으로도 충분히 만족하고 살 것 같다.

Creative Selection 표지

Ken Kocienda, Creative Selection, St. Martin's Press, 2018

애플에 일하며 진행한 프로젝트를 일화처럼 이야기하며 애플 내에서 어떤 방식으로 개발 프로세스가 진행되는지 풀어간 책. 프로그래밍을 깊게 알지 못하더라도 일상적인 용어로 쉽게 풀어서 설명하는 과정이 인상적이다. 단순하게 애플의 문화를 설명하는 것 이외에도 프로덕트를 개발하는 과정이 어떤 흐름에서 이뤄지는지 설명하고 있어서 IT 직군에 대한 궁금증이 있다면 재미있게 읽을 수 있다.

iOS 기기 개발 초기는 어땠나도 세세하게 나와서 재미있었지만 Safari가 어떤 과정으로 나타난 프로덕트인지도 재미있는 부분이었다. 글에서는 간단하고 흥미롭게만 쓰였지만 분명 지나간 일이라 이렇게 아름답게 쓸 수 있었을 것이라 생각도 들고. 중간 중간에 어떤 결단들이 이 사람의 커리어에 어떤 영향을 줬는지도 인상적이다.

학생으로 돌아온 이후로 실무에 대한 막연한 그리움이 있었는데 이 책을 읽으면서 프로젝트를 진행하고 결과를 내는 즐거움을 그리워하고 있다는 걸 알게 되었다. 물론 지금도 자잘한 코드를 만들긴 하지만... 함께 만들고 누군가에 도움이 되는 그런 즐거움이 그립다.

Hard work is hard. Inspiration does not pay off without diligence. We collaborated to get through the drudgery. -- p.88

I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what's next. -- p.133

I described empathy as trying to see the world from other people's perspectives and creating work that fits into their lives and adapts to their needs. Empathy is a crucial part of making great products. -- p.182

Taste is developing a refined sense of judgement and finding the balance that produces a pleasing and integrated whole. -- p.183

Our goal was to orchestrate a progression of algorithms and heuristics to create great products that would put smiles on people's faces and would function well without fuss. Design is, after all, how it works. -- p.245

Published on March 11, 2022
Published on February 2, 2022
게이미피케이션 책 표지

게이미피케이션의 대략적인 개념을 이해하는데 도움이 됐다. 이 책에서는 게임의 재미가 메커니즘에서 오며 도전과 성취, 보상의 순환 구조 속에서 매력을 느낀다고 설명한다. 어떻게 참가자가 플레이어로 변화하고 시스템 내에서 장기적으로 존재하기 위해서 어떤 기법과 역학을 활용해야 하는지 설명한다.

게이미피케이션에서 사용하는 기법을 이야기하는데 각 기법에 대해 깊은 설명보다 사례 위주로 정리하는 편이다. 소셜 몰입 루프, 게임 요소 목록, 텅 빈 주점 문제를 해결하는 방법이 인상적이었다.

마지막엔 실습 부분도 있지만 거기서도 구현에만 치중되어 있고 디자인에서 왜 그런 결정을 내려야 하는지에 대한 설명이 부족했다. 대부분 섹션에서 상세한 내용은 자신의 웹페이지를 인용하는데 그 웹사이트가 없어진 탓에 반쪽짜리 책이 되었다. 아무래도 오래된 책이긴 하더라도 얇은 책인데 더 얇게 느껴졌다. 그래도 핵심적인 부분에 대해서는 다 다루고 있기 때문에 간단하게 보기에는 좋은 책이다.

책 내에서 추천한 책: Jesse Schell 의 The Art of Game Design: A book of lenses (Amazon, 알라딘)

게임 메커니즘

  • 점수
  • 레벨
  • 리더보드: 집계 방식이 사생활을 침해하지 않도록 유의
  • 배지
  • 온보딩 (초보자 적응 프로그램)
  • 도전과제와 퀘스트
  • 소셜 몰입 루프
    • 가시적인 성장과 보상 -> 감성적인 자극 -> 소셜 행동 요구 -> 플레이어의 재몰입 -> (가시적인 성장과 보상) -> (...)
  • 맞춤화: 최소 요구 사항만 만족하기
  • 대시보드
  • 피드백과 강화: 흐름 범위 내에서

사람들이 좋아하는 요소

  • 패턴인식: 퍼즐, 카드 뒤집기
  • 수집: 흔한 아이템에서 한정 아이템까지
  • 놀라움과 의외의 즐거움: 슬롯머신
  • 정리하고 질서 잡기: 심시티
  • 선물하기: 애니팡 하트
  • 집적대기와 로맨스: 찔러보기 (페이스북)
  • 성과 인정: 배지, 트로피
  • 리더십: 팀 과제
  • 명성 얻기와 주목 받기: 리더보드
  • 영웅 되기: 도와주세요 메시지, 제한된 시간
  • 지위 얻기: 배지, 트로피 (공개된 프로필)
  • 육성, 성장: 다마고치

(게이브 지커맨, 크리스토퍼 커닝햄 지음, 정진영, 송준호, 김지원 옮김, 한빛미디어, 2012년)

Published on January 11, 2022

예전에 비해 사진기를 드는 횟수가 훨씬 적어졌다. 사진에 흥미가 떨어지거나 한 것은 아니다. 다만 환경이 달라져서 무엇을 촬영해야 할지에 대한 막막함이 컸었다. 삶도 바쁘게 돌아가서 그런 고민 자체를 할 여유가 없었기도 했다.

그런 중에 작년 초에 교양 과목으로 필름 사진 수업을 듣게 되었다. 계획하고 촬영하고 최종 편집하는 과제를 몇 번 반복하다보니 아 내가 이래서 사진 찍는걸 좋아했지 생각이 들었다. 특히 수업이 도움이 되었던 건 미국에서 활동하는 많은 작가들의 작품을 다채롭게 접할 수 있었던 부분이다. 사진에 관심이 많아서 책도 보고 다큐멘터리도 보고 그랬지만 수업에서 얻을 수 있는 것과는 확연히 달랐다. 수업을 듣고 난 이후에 사진을 더 꾸준히, 일상적으로 더 많이 남겨야겠다는 생각이 들었다.

Sony A7R2는 초기에 구입해서 중요한 사진을 촬영해야 하는 상황에 잘 활용하고 있다. 다만 카메라 무게도 무겁고 부피도 큰 편이라서 항상 가방에 들고 다니기는 부담스러웠다. 이미 E 마운트 렌즈가 있으니 동일 렌즈 마운트를 사용하면서도 작은 부피의 카메라가 필요했다.

  • 부피 작을 것
  • 수동 렌즈 많이 사용해서 AF가 빠를 필요 없음
  • 대신 wifi로 접속해서 사진 즉석에서 꺼낼 수 있어야 함
  • 적당한 화소 (적어도 지금 쓰는 폰보다는 나을 것)
  • A7R2와 호환되는 배터리

한참 비교하다가 Sony a5000 중고를 저렴한 가격에 구입하게 되었다. 예전에 a6000 쓸 때는 만족 못해서 후회하지 않을까 걱정했는데 결과는 대만족이다.

RAW+JPG로 촬영하긴 하지만 후보정 최대한 안하도록 설정에 신경을 썼다. 암부에서 나타나는 노이즈가 필름과 비슷한 느낌이 들어서 마음에 든다. 후보정 하게 되면 채널 커브에서 Black 부분만 올려주는 것으로 충분하다. 정착한 설정은 아래와 같다.

  • 화이트밸런스: Auto A1 M1 또는 Daylight A1 M1
  • 다이나믹 레인지 최적화(DRO): Lv 3
  • 컬러 스페이스: AdobeRGB
  • 크리에이티브 스타일: Vivid (Contrast +2 Saturation -1 Sharpness +3)
  • ISO 400
  • 셔터스피드 자동

별도로 렌즈가 적히지 않은 사진은 Voigtlander Nokton Classic 35mm F1.4 I SC로 촬영했다.

김밥 사랑. 비닐 질감이 이쁘다.
DRO를 켜면 암부 노이즈도 크게 거슬리지 않는다.
Sony 85mm F1.8
트래픽에 서 있을 때 주변을 찍기도
헬리코이드 어댑터 사용
단일 코팅 렌즈라서 역광에 약한 편
막 자란 선인장이 귀엽다.
겨울에 해질 즈음 풍경은 늘 아름답다.
호주 못잊어 진저비어에 취한다 🥲
아름다운 노을 넋 놓고 보고
Sony 85mm F1.8

가끔 화이트밸런스를 제대로 못잡는 경우가 있는데 그 자체도 약간 주광 필름을 안맞는 조명에서 쓰는 느낌처럼 보여서. 약간 엉성한 화이트밸런스라 덜 기계적인 사진이 나온다. 그리고 예전엔 ISO를 AUTO로 놓고 쓰는 편이었는데 ISO 400에 놓으니 적당히 필름 그레인 정도로 입자가 보여 좋다. 예전엔 불편하다고 생각했을 법한데 선호하는 미감이 달라진 것도 분명하다.

Published on January 10, 2022

정말 바쁘게 2021년을 보냈다. 수업 하나가 정말 힘들었는데 기말까지 다 끝내고 나서도 점수 걱정에 스트레스가 많았다. 1월이야 돼서야 해가 지나간 것이 조금씩 실감나기 시작했다. 이런 저런 일이 많았고 심적으로 쉽지 않았던 해였지만 잘 끝낸 것에 감사하다.

학업

  • 커뮤니티 컬리지 3학기 (봄, 여름, 가을) 수강
    • 가을 학기가 이 학교에서 마지막 학기였다. 2019년 가을부터 총 95유닛을 수강했다.
    • 이제 편입 지원하고 결과를 기다리고 있다. 아직 지원이 다 끝난 것 아니라서 당분간 편입 서류에 바쁠 예정이다.
    • 마지막까지 높은 GPA를 유지할 수 있어서 얼마나 다행인지. 편입도 좋은 결과로 이어졌음 좋겠다.
    • IGETC를 마무리했다! (CS, Math, Physics AS-T)
  • 온라인/하이브리드 수업
    • 벌써 온라인으로 전환된지 2년차인데 이번 가을 학기는 하이브리드로 일부 출석 수업이 있었다. 학교 나가는게 얼마나 즐거운 일인지.
    • 온라인 오프라인 수업 모두 잘 하는 분이 있는가 하면 그냥 유튜브 링크만 올려주는 수업도 있었다. 점수를 잘받는 것과 무언가를 배워 가는 것 사이에서의 괴리.
  • 수학이랑 물리에서 재미 찾기
    • 과목이 재밌다고 느끼는건 결국 어떻게 가르치냐 너무 중요한 것 같다.
    • 봄학기엔 수학 너무 힘들었고 가을학기엔 물리가 참 힘들었다.

프로젝트

  • 웹사이트 수정
    • 이번엔 이론 수업이 많아서 학기 내에 코드를 작성할 일이 거의 없었다. 뭔가 프로젝트로 만들기에는 들어야 할 수업과 과제가 쏟아지는데 잠깐 코드를 할 만한 그런건 결국 웹사이트 만지는 정도.
    • 크게 달라지진 않았지만.
  • 타이머 업데이트
    • 학기가 끝나고 나서 타이머를 업데이트했다. 그동안 라이브러리가 많이 달라진 탓에 수정해야 할 부분이 많았다.
    • 이제 앞으로 방향을 어떻게 잡고 기능을 추가할지 고민하고 있다.

여행

  • 시애틀
    • 코비드 걱정이 커서 여행은 커녕 외출도 잘 안하고 있었는데 민경씨와 나 둘 다 너무 스트레스 받고 있어서 짧게나마 시애틀을 다녀왔다.
    • 비가 연중 내리지 않는 동네에 살아서 여행 내내 비도 오고 그랬지만 너무 좋았다.
    • 기대한 것보다 더 아기자기하고 귀여웠던 동네.
    • 커피 맛있었다. 지나치게 많은 카페인을 섭취했지만 여행이니까 괜찮다고 서로 얘기하면서 또 커피를 마셨다.
    • 반가운 사람들 봐서 좋았다. 처음 봐도 오래 본 것 같은 기분.
    • UW도 둘러봤다. 너무 이쁜 캠퍼스, 분위기에 반했다.
  • 텍사스
    • 처제네 방문이 잦아져서 텍사스는 자주 가게 되었다.
    • 매일 조카 크는 것 보는 재미에 온 가족이 즐거워하고 있다.
    • UT Austin도 둘러봤다. 도서관이 인상적이었다.

건강

  • 염증으로 치아 제거 및 임플란트
    • 갑자기 얼굴이 2배가 될 만큼 부어올라서 급하게 ER을 가게 되었다.
    • 발치 이후에도 밥도 잘 못먹을 정도로 아파서 몇주 고생했다.
    • 민경씨가 이 시기에 특히 고생해서 많이 미안했다.
  • 코비드 백신 3차까지
    • 백신 맞았지만 여전히 외출은 자제하고 있다.
  • 운동 거의 못함
    • 연초에는 저스트 댄스 부지런히 했는데
    • 체력이 부족해서 그런지 스트레스에 더 무력했던 것 같다.
  • 새 양치 방법
    • 박창진 치과의사님 영상을 본 이후로 양치 방법도 바꾸고 치간칫솔도 사용하기 시작했다.
    • 여기에 이걸 왜 적냐면... 대만족해서 그렇습니다.

취미

  • 목공
    • 이사를 갈 가능성이 높아져서 짐을 줄여야 할 상황이라 더 만들지 않고 있다.
  • 트럼펫
    • 처음에는 전혀 소리도 나지 않아서 답답했는데 결국 꾸준하게 연습하는 것 말고는 방법이 없었다.
    • 레슨은 온라인으로 두 차례 정도 가졌는데 연습 루틴을 짜고 그 루틴 따라서 연습하고 있다.
    • 롱톤, 롱톤, 롱톤. 모든 관악기의 운명일까.
  • 식물 키우기
    • 식물 잘 죽이는 편이라서... 쉽게 잘 자라는 애들만 키우고 있다.
    • 스트레스 받는다고 트윗멍하면 스트레스 더 받는 얘기가 많아서. 학기 중에 식물멍 하는게 도움이 많이 되었다.
  • 사진
    • 예전에 비해 피사체가 많이 달라졌다. 2021년엔 가족행사 사진을 정말 많이 찍었다.
    • 그동안 큰 렌즈를 쓸 일 없어서 안샀는데 행사용으로 Sigma 35mm 장만하고 조명킷도 마련하게 되었다.
    • 봄학기에 사진 수업을 들어서 Canon AE-1 구입했다. 전자식이라서 베터리가 필수지만 그래도 갖고 있던 FD 렌즈를 다 활용할 수 있어서 좋았다.
  • 일상에서 간단하게 들고 다니면서 사용하기 위해 Sony a5000 구입했다.
    • a7r2는 아무래도 부피가 커서 그냥 가방에 넣어놓고 다니기엔 부담스러웠다. 그렇다고 폰으로 찍자니 화질이 만족스럽지 않을 때가 많아서...
    • 소니 렌즈를 다 활용할 수 있는 기종 중 저렴해서 막 쓰기 좋은 기종을 고민하다가 결정했는데 만족스럽다.

감정

  • 학업 스트레스가 컸음
    • 봄학기도 가을학기에도 수업 하나씩 정말 괴롭게 만드는 일이 있었다.
    • 편입 경쟁률 높은 학과 들어가려면 내가 쓸 수 있는 전략이야 점수 높게 받는 것 외에는 크게 없는 것 같았다.
      • 자연스럽게 점수 하나에 일희일비 하게 되었는데. 여유가 없어서 더 스트레스를 많이 받은 것 같다.
    • 현업에서 벗어난 순간부터 공부를 하는게 맞나 고민을 끊임없이 하고 있는데 나만 제자리에 있는 기분은 정말 괴롭다.
    • 당면한 수업과 과제가 내 진로와 방향성이 맞지 않는다는 생각과 내가 기대하는 것과의 괴리에서 오는 스트레스도 컸다.
      • 내가 시간을 제대로 쓰고 있는 것일까.
  • 조각모음
    • 회고 사이클을 짧게 해서 방향을 계속 바로잡자는 생각으로 시작했다.
    • 생각보다 매월 정리하는 일이 쉽지 않았다.
      • 일상은 반복되는 구석이 많아서 쓸 내용이 고민이었다.
      • 아무래도 학교를 다니고 있으니 수업 듣고 과제 몇 차례 하면 한 달은 금방이었다.
    • 써야지 생각은 하면서도 그 조금 시간 내기가 힘들어서 2, 3달 몰아서 하기도 했다.
    • 연말엔 편입 서류 준비와 수업에 정신 없이 보내서 적지 못했는데 아쉽다.
    • 2달마다 하면 적당할 것 같은데 양식을 더 다듬을 필요가 있는 것 같다.
  • 수첩 사용
    • 플래너도 쓰고 앱도 쓰고 그랬는데 올해는 처음부터 수첩 하나에 모든 것 적고 관리하기로 마음 먹고 시작했다.
    • 무선 노트라서 내가 쓰는 것이 양식이 되서 편했다. 필요하면 더하고 안쓰면 빼고. 물론 매번 양식을 적는 일은 귀찮다.
    • 색인이 어렵지만 스티커 탭 붙이면 별 문제 없었다.
    • 그동안 모든 기록을 앱이나 컴퓨터에 했는데 종이에 적는건 무엇보다도 양식 없이 자유롭게 쓸 수 있어서 새로운 경험이었고 만족스럽다.
    • 다른 장점은 폰을 멀리 둬도 문제 없다는 점. 사소한 것 확인한다고 폰을 켜면 유혹이 너무 많다. 스트레스를 받는 상황이라면 게으름 부릴 틈을 안주는 것도 할 일 먼저 하는데 도움이 되는 것 같다.
      • 차라리 제대로 쉴 때 보는게 낫다. 중간 중간 단순한 충동으로 나중에 봐도 상관 없는 것 찾아보는 일, 별로 쉼도 안되고 건강하지 못한 행동 같아서.

고민

학교에 등록하는 순간부터 한 고민인데 수첩에 적어놓고 항상 생각했던 질문이다.

  • 아래 항목이 같은 방향으로 정렬되어 있는가?
    • 지금 하는 일 (하고 있는 공부, 시간 사용하고 있는 일)
    • 가까운 미래에 하는/할 일 (취미, 직업, 활동)
    • 장기적으로 바라보는 내 모습/상태/상황 (가정, 성취, 직함, 명성/명예)

어느 하나에 매몰되어 다른 항목에서 균형을 잃어버리지 않는 일이 너무 중요하다. 이 질문으로 계속 돌아갈 수 밖에 없던건 깨진 균형을 자꾸 보게 된 탓이 크다. 새해에는 무너지지 않기로.

2022년 목표

  • 심적 여유 찾기: 조급함 내려놓고 고민보다 행동으로 옮기는 삶 살자. 긍정적으로 말하고 행동하자.
  • 책읽기: 반려자님과 독서 계획을 세웠다. 저녁에 그냥 흘려보내는 시간이 많아서 서로 걱정하던 차였는데 함께 많이 읽을 수 있으면 좋겠다.
  • 운동: 올해는 꾸준히 습관 만드는 것을 목적으로 애플 워치 목표를 꾸준히 달성하는 것을 목표로 세웠다.
  • 여름학기를 가득 들은 덕분에 편입 전 한 학기를 벌었다. 이 기간에 무엇을 할지 오래 고민했는데 결국 그 시간이 될 때까지 아무것도 결정하지 못했다. 잠깐이라도 일을 구해서 하고 여행도 가고 할 생각이었는데 막상 코비드에 바쁜 삶 살다보니. 이번 달에 좀 주변 정리와 함께 결정하고 싶다.
  • 월간 조각모음을 좀 더 잘 해보고 싶다. 올해도 매월 밀리지 않고 했으면 좋겠다.
  • 수첩에 생각을 정리하는 습관을 만들고 싶다. 생각 없이 인터넷, 소셜 들여보는 시간을 좀 줄이는 것으로 이어졌음 좋겠다.

계획을 세세하게 정하고 싶지만 아직 학기 후에 밀려오는 감정적 소요가 커서 좀 여유를 갖기로 했다. 올해는 많은 변화가 있을 예정이다. 편입 결과에 따라 다음 회고는 어디서 쓰게 될지 달라질 것 같다. 그동안 열심히 했는데 좋은 결과로 잘 이어졌으면 좋겠다.

iOS 앱을 개발하다보면 Xcode에서 기기를 제대로 인식하지 못해서 연결이 되었다 말았다 하는 증상을 보일 때가 있다. Xcode나 기기를 껐다 켜면 해결된다는 글을 예전에 보고 그렇게 해왔는데 한참 개발하고 있을 때 문제가 생기면 엄청 번거롭다. 케이블이 문제라는 글도 있었는데 결국 다 변죽 울리는 얘기고 문제는 usbd였다.

이전에 연결되어 있던 usbd이 죽지 않고 정지된 상태로 대기중일 때 이런 문제가 발생한다. 터미널에서 다음 명령어로 해당 데몬을 끄면 정상적으로 동작한다.

$ sudo killall -STOP -c usbd

색상을 바꿔요

눈에 편한 색상을 골라보세요 :)

Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.