PHP

[PHP] Constructor Property Promotion (생성자 속성 승격)

어렵지만 2025. 10. 27. 13:22

생성자 속성 승격은 PHP 8.0부터 도입되었습니다. 클래스의 속성을 선언하고 생성자에서 그 값을 초기화 하는 반복적인 코드를 한줄로 줄여주는 기능입니다.

 

기존에는 클래스 상단에 속성을 선언, 생성자 매개변수를 받아 생성자 본문에서 $this -> $매개변수; 와 같이 일일이 할당 해야했습니다.

 

기존

class Point
{
    public float $x;
    public float $y;
    public float $z;

    public function __construct(
        float $x,
        float $y,
        float $z
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

위 코드를 보면, $x, $y, $z라는 이름이 각각 3번씩 반복해서 나타납니다.
속성 선언 (public float $x;)
생성자 매개변수 (float $x)
값 할당 ($this->x = $x;)
이는 코드를 길고 지루하게 만들며, 속성을 하나 추가하거나 제거할 때 여러 곳을 수정해야 하는 번거로움이 있었습니다.

 

이번에는 생성자 속성 승격을 사용하보겠습니다.

class Point
{
    public function __construct(
        public float $x,
        public float $y,
        public float $z
    ) {
        // 생성자 본문이 비어있어도 됩니다!
    }
}

// 사용법은 완전히 동일합니다.
$point = new Point(1.0, 2.0, 3.0);
echo $point->x; // 1.0

 

생성자 매개변수 앞에 public, proteted, private같은 가시성 제어자를 붙여 사용합니다.
PHP는 이를 보고 다음 세가지 작업을 자동으로 수행.

 

1. 동일한 이름의 클래스 속성을 선언

2. 생성자 매개변수로 값을 받는다.

3. 받은 값을 해당 속성에 자동으로 할당.

 

주요 규칙 및 문법
가시성 제어자 필수: public, protected, private 중 하나가 반드시 매개변수 앞에 있어야 승격이 일어납니다.
생성자에서만 사용 가능: 이 구문은 오직 클래스 생성자(__construct)에서만 사용할 수 있습니다.
var 사용 불가: 오래된 var 키워드는 사용할 수 없습니다.
중복 선언 불가: 승격된 속성과 동일한 이름의 클래스 속성을 별도로 선언할 수 없습니다.

class User {
    public string $name; // 오류!

    public function __construct(public string $name) {} // $name이 중복 선언됨
}

일반 매개변수와 혼용 가능: 승격된 속성과 일반 매개변수를 함께 사용할 수 있습니다.

class Logger
{
    public function __construct(
        public string $logFile, // 승격된 속성
        bool $clearOnInit      // 일반 매개변수
    ) {
        if ($clearOnInit) {
            file_put_contents($this->logFile, '');
        }
    }
}

 

어트리뷰트(Attribute) 사용 가능: 승격된 속성에도 어트리뷰트를 붙일 수 있습니다.

 

class User {
    public function __construct(
        #[MyAttribute]
        public string $name
    ) {}
}

 

readonly 키워드와 함께 사용하면 생성 시에만 값을 할당할 수 있는 불변객체를 간결하게 만들 수 있습니다.

 

// PHP 8.1+
class UserProfile
{
    public function __construct(
        public readonly int $id,
        public readonly string $username,
        public string $displayName // displayName은 변경 가능
    ) {}
}

$profile = new UserProfile(101, 'john_doe', 'John Doe');
echo $profile->username . PHP_EOL; // john_doe

// $profile->username = 'jane_doe'; // 치명적 오류 발생! (Fatal error: Uncaught Error: Cannot modify readonly property)

$profile->displayName = 'John D.'; // OK. readonly가 아니므로 변경 가능
echo $profile->displayName . PHP_EOL; // John D.

 


부모 클래스의 생성자를 호출하면서 자신만의 승격된 속성을 가질 수 있습니다.

class Product
{
    public function __construct(
        public readonly string $sku,
        public float $price
    ) {}
}

class Book extends Product
{
    public function __construct(
        // 자식 클래스만의 승격된 속성
        public readonly string $title,

        // 부모 생성자로 전달할 일반 매개변수
        string $sku,
        float $price
    ) {
        // 부모 생성자 호출
        parent::__construct($sku, $price);
    }
}

$book = new Book('The Pragmatic Programmer', 'BK-12345', 49.99);

echo "제목: " . $book->title . PHP_EOL;
echo "SKU: " . $book->sku . PHP_EOL;
echo "가격: " . $book->price . PHP_EOL;
?>

 

요약을하자면

1. 코드 간결성: 반복적인 코드를 줄여 클래스의 정의가 깔끔해짐.

2. 가독성: 속성의 선언과 초기화가 한 곳에 있어 클래스의 구조를 파악하기 용이

3. 유지보수: 속성을 추가하거나 수정할 떄 생성자나 시그니처 한 줄 만 변경하면 됨으로 편리