- 2
- 네모
- 조회 수 830
이번에도 때늦은 PHP8.0 소식으로 돌아왔습니다.
PHP8에서는 클래스 생성자에서 바로 멤버 변수를 선언할 수 있다는 모양인데요!
(멤버변수보다는 프로퍼티, 속성 이라는 명칭을 더 애용합니다만, 이전 포스트의 Attribute(속성)과의 혼동을 방지하기 위해 멤버변수, 라고 하겠습니다. 음음.)
Constructor promotion
클래스 생성자에서 멤버 변수의 값을 지정하려면, 아래와 같은 방법을 사용해야 했습니다.
class LeagueOfBackend
{
public string $name;
public int $rank;
public function __construct(string $name, int $rank)
{
$this->name = $name;
$this->rank = $rank;
}
}
이렇게 패러미터로 전달받은 값들을, $this->isnotthat = $value;
형태로 다시 멤버 변수에 재매핑 해줘야 했습니다.
감동도 없고, 멋있지도 않고, 우아하지도 않은, 짖굳게 표현하자면 브론즈 같은 표현이지요.
(필자는 아이언입니다.)
이런 구리구리한 표현을 PHP8에서는 좀 더 이쁘게 바꿔 사용할 수 있습니다.
class PHPisLegeonseol
{
public function __construct(
public string $name,
public string $rank
)
{
}
}
이러한 표현법은 PHP 엔진에서 실제로 코드를 실행하기 전, 기존구문으로 변경하여 처리됩니다.
큰 부하가 생기지는 않겠지만, 기존 구문에 비해 어느정도 딜레이가 생길수 있다는 점은 파악하고 있어야겠지요.
생성자에서만!
당연하다면 당연한 이야기겠지만, 멤버 변수 승격은 생성자에서만 작동합니다.
생성자를 직접 호출하면...?
생성자에서만 작동하도록 제한한 것에 비해 조금 아이러니한 내용이 있습니다.
class Foo
{
public function __construct(
public int $number
)
{
}
public function bar(int $number)
{
$this->__construct($number);
}
}
위 코드와 같이 생성자를 직접 호출하게 되면, $this->number
의 값이 변하게 됩니다.
사실 기존 구문으로 변경하여 처리하는 방식이다보니 이해는 합니다만...
그럴거면 차라리 아예 모든 메서드에서 사용할 수 있게 풀어주고 개발자들이 알아서 잘 사용하도록 처리해도 괜찮았을 듯 한데, 조금 아쉬운 면이 있네요.
중복 선언은 허용안됨
실행 시점에 단순히 구문을 변경해 주는 방식으로 작동하기 때문에, 몇가지 제약사항(?)이 생깁니다.
class NoDuplicate
{
public int $number;
public function __construct(
public int $number // (사용불가)
)
{
}
}
위 코드와 같이 이미 클래스의 변수로 사용된 이름은, 승격 변수에서 사용할 수 없습니다.
바로 아래에서 설명할 내용이지만, 기본값을 전달할 수 있어 큰 제약이 되지는 않을것 같습니다.
기본값 전달 가능
class DefaultValue
{
public function __construct(
public int $number = 10
)
{
}
}
기본값을 전달할 수 있습니다.
여타 다른 함수에서도 불가능하듯, 단순값(int
, string
, bool
등)이 아닌 표현식(new ...()
) 는 전달할 수 없습니다.
Reflection 관련
class PromoteDocblock
{
public function __construct(
/** @var int */
public int $number
)
{
}
}
DocBlock 도 같이 승격됩니다. Reflection API 를 통해 DocBlock 을 받아올 수도 있습니다.
Attribute 가 생긴 이상 DocBlock Annotation 은 점차 사라지겠지만, 아무튼 가능해요.
class PromoteAttribute
{
public function __construct(
#[FoobarAttribute]
public int $number
)
{
}
}
Attribute 도 당연히(?) 승격됩니다. 마찬가지로 Reflection API 에서 접근할 수 있구요.
class CheckWithReflection
{
public function __construct(
public int $number
)
{
}
}
$reflector = new ReflectionProperty(CheckWithReflection::class, 'number');
var_dump($reflector->isPromoted()); // bool(true)
$reflector = new ReflectionParameter(['CheckWithReflection', '__construct'], 'number');
var_dump($reflector->isPromoted()); // bool(true)
ReflectionProperty
클래스와 ReflectionParamter
클래스에는 isPromoted()
메서드가 추가되었습니다.
해당 메서드를 통해 해당 멤버변수 또는 패러미터가 승격된 친구인지 아닌지 확인 가능합니다.
일반 패러미터와 혼합 사용
class Wigitalchul
{
public function __construct(
public int $number,
int $one
)
{
}
}
이쁘지는 않지만, 일반 패러미터와 혼합하여 사용할 수도 있습니다.
신규 구문을 사용하지 않고, 기존 구문을 사용하는 방향이 관리 측면에서 더 깔끔하지 않을까 생각합니다.
적용 가능 대상
class NormalClass
{
public function __construct(
public int $number,
)
{
}
}
일반적인 클래스에서는 당연히 됩니다.
class ParentClass
{
public function __construct(
public int $number,
)
{
}
}
class ChildClass
{
public function __construct(
int $number,
)
{
parent::__construct($number);
}
}
부모 클래스에서도 사용할 수 있습니다.
단, 자식 클래스에서는 수동으로 전달해 주어야 합니다.
이게 가능한게 맞나...?
trait TheTrait
{
public function __construct(
public int $number,
)
{
}
}
trait
에서도 사용할 수 있습니다.
다만, 아래 상황에서는 주의하여 사용해야 합니다.
trait ATrait
{
public function __construct(
public int $number = 1,
)
{
}
}
trait BTrait
{
public int $number = 1;
}
class TheClass
{
use ATrait, BTrait;
}
위와 같이 다중 트레이트 적용 시
승격받는 변수의 기본값이 지정되어 있는 경우 타 트레이트의 기본값과 동일하더라도 충돌이 일어납니다.
기본값을 아예 쥐어주지 않으면 충돌이 일어나지 않는데, 왜 그런지는 모르겠네요.
abstract Kandinski
{
abstract public function __construct(
public int $number,
)
{
}
}
추상 생성자에서는 작동하지 않습니다.
추상 클래스의 생성자가 아니라, 추상 클래스의 추상 생성자 입니다.
그래서 추상생성자는 대체 뭐죠?
기타
사실 개발에 엄청난 도움을 주는 큰 변화는 아니라고 생각합니다.
심지어 내부에서는 기존 구문으로 변환하는 절차를 거치는데다, 자주 사용하다보면 실수하기 쉬운 부분들도 조금 보이구요.
그런 면에서 볼 때, 전면적으로 도입(?)하기에는 메리트가 부족하지 않나, 라는 생각이 많이 드는 구문인 것 같습니다.
그럼에도 이러한 신 구문(특히 최신 트렌드인 언어에서 도입되는 구문)들이 점점 늘어나면서,
모던 PHP 이후의 또 다른 새로운 바람이 불어 PHP의 생태계가 다시금 확장되지 않을까 기대합니다.