PHP 클래스 속성

PHP 클래스 속성 (Properties)

원문: PHP 공식 매뉴얼 — Language Reference > Classes and Objects > Properties 이 문서는 PHP 공식 매뉴얼 language.oop5.properties 페이지를 한국어로 옮긴 내용이에요.

클래스 안에서 데이터를 담아 두는 변수를 우리는 속성(property) 이라고 불러요. 다른 언어에서는 필드(fields) 같은 말로 쓰기도 하지만, 이 문서에서는 계속 속성이라는 표현을 쓸게요. 속성은 최소한 하나의 수식자(modifier)를 붙여서 정의하는데, 그 수식자가 바로 가시성(Visibility)이나 static 키워드이고, PHP 8.1.0부터는 readonly를 쓸 수도 있어요. 그리고 PHP 7.4부터는 (readonly 속성만 빼고) 선택적으로 타입 선언을 붙이고, 그다음 일반 변수 선언이 이어져요. 이 선언에 초기값을 넣을 수도 있는데, 그 초기값은 반드시 상수(constant) 값이어야 해요.

Note:

클래스 속성을 선언하는 옛 방식으로는 수식자 대신 var 키워드를 쓰는 방법이 있었어요. 이제는 쓰이지 않는 방식이에요.

Note:

가시성 수식자 없이 선언한 속성은 public으로 취급돼요.

클래스 메서드 안에서는 비-정적 속성에 ->(객체 연산자)로 접근해요. $this->property처럼 쓰면 되고, 여기서 property는 속성 이름이에요. 정적 속성은 ::(더블 콜론)으로 접근해요. self::$property처럼 쓰죠. 정적 속성과 비-정적 속성의 차이가 더 궁금하면 static 키워드 문서를 봐주세요.

의사 변수(pseudo-variable) $this는 어떤 클래스 메서드든 그 메서드가 객체 컨텍스트 안에서 호출될 때 사용할 수 있어요. $this는 바로 그 호출을 하고 있는 객체 자신을 가리켜요.

예제 #1 — 속성 선언하기

<?php
class SimpleClass
{
   public $var1 = 'hello ' . 'world';
   public $var2 = <<<EOD
hello world
EOD;
   public $var3 = 1+2;
   // invalid property declarations:
   public $var4 = self::myStaticMethod();
   public $var5 = $myVar;

   // valid property declarations:
   public $var6 = myConstant;
   public $var7 = [true, false];

   public $var8 = <<<'EOD'
hello world
EOD;

   // Without visibility modifier:
   static $var9;
   readonly int $var10;
}
?>

Note:

클래스와 객체를 다루는 함수가 여러 가지 준비되어 있어요. 클래스/객체 함수 문서를 참고하세요.

타입 선언 (Type declarations)

PHP 7.4.0부터 속성 정의에도 타입 선언을 넣을 수 있어요. 단, callable 타입은 속성에 쓸 수 없어요.

예제 #2 — 타입이 있는 속성 예시

<?php

class User
{
    public int $id;
    public ?string $name;

    public function __construct(int $id, ?string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}

$user = new User(1234, null);

var_dump($user->id);
var_dump($user->name);

?>

위 예제는 이렇게 출력돼요.

int(1234)
NULL

타입이 있는 속성은 접근하기 전에 반드시 초기화해야 해요. 그렇지 않으면 Error가 던져져요.

예제 #3 — 속성 접근하기

<?php

class Shape
{
    public int $numberOfSides;
    public string $name;

    public function setNumberOfSides(int $numberOfSides): void
    {
        $this->numberOfSides = $numberOfSides;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getNumberOfSides(): int
    {
        return $this->numberOfSides;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

$triangle = new Shape();
$triangle->setName("triangle");
$triangle->setNumberOfSides(3);
var_dump($triangle->getName());
var_dump($triangle->getNumberOfSides());

$circle = new Shape();
$circle->setName("circle");
var_dump($circle->getName());
var_dump($circle->getNumberOfSides());
?>

위 예제는 이렇게 출력돼요.

string(8) "triangle"
int(3)
string(6) "circle"

Fatal error: Uncaught Error: Typed property Shape::$numberOfSides must not be accessed before initialization

읽기 전용 속성 (Readonly properties)

PHP 8.1.0부터 속성을 readonly 수식자로 선언할 수 있어요. 이렇게 선언한 속성은 초기화된 뒤에는 값을 바꿀 수 없어요. PHP 8.4.0 이전에는 readonly 속성이 암묵적으로 private-set이라 같은 클래스 안에서만 값을 쓸 수 있었는데, PHP 8.4.0부터는 암묵적으로 protected(set)이 되어서 자식 클래스에서도 설정할 수 있게 됐어요. 원한다면 이 설정을 명시적으로 바꿀 수도 있어요.

예제 #4 — 읽기 전용 속성 예시

<?php

class Test {
   public readonly string $prop;

   public function __construct(string $prop) {
       // Legal initialization.
       $this->prop = $prop;
   }
}

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

// Illegal reassignment. It does not matter that the assigned value is the same.
$test->prop = "foobar";
// Error: Cannot modify readonly property Test::$prop
?>

Note:

readonly 수식자는 타입이 있는 속성에만 적용할 수 있어요. 타입 제약이 없는 readonly 속성은 Mixed 타입으로 만들 수 있어요.

Note:

readonly 정적 속성은 지원하지 않아요.

readonly 속성은 딱 한 번, 그리고 속성이 선언된 스코프에서만 초기화할 수 있어요. 그 외의 어떤 할당이나 변경도 Error 예외를 만들어내요.

예제 #5 — 읽기 전용 속성의 잘못된 초기화

<?php
class Test1 {
    public readonly string $prop;
}

$test1 = new Test1;
// Illegal initialization outside of private scope.
$test1->prop = "foobar";
// Error: Cannot initialize readonly property Test1::$prop from global scope
?>

Note:

readonly 속성은 직접 대입(direct assignment)으로만 초기화할 수 있어요. 초기화되지 않은 readonly 속성은 참조(reference)로는 초기화할 수 없는데, 선언된 스코프 안에서조차 안 돼요. 참조로 전달받는 함수의 인자로 넘기거나, 참조로 대입하려고 하면 Error 예외가 나요.

<?php

class Test {
    public readonly array $matches;

    public function __construct(string $subject) {
        // Illegal initialization by reference.
        preg_match('/\d+/', $subject, $this->matches);
        // Error: Cannot indirectly modify readonly property Test::$matches
    }
}

new Test('abc 42');
?>

Note:

readonly 속성에 명시적인 기본값을 지정하는 건 허용되지 않아요. 기본값이 있는 readonly 속성은 사실상 상수와 같아서 그다지 쓸모가 없거든요.

<?php

class Test {
    // Fatal error: Readonly property Test::$prop cannot have default value
    public readonly int $prop = 42;
}
?>

Note:

초기화된 readonly 속성은 unset() 할 수 없어요. 다만 초기화 전에는, 속성이 선언된 스코프 안에서라면 unset() 하는 게 가능해요.

값을 바꾸는 게 반드시 단순한 할당만을 뜻하지는 않아요. 아래 나오는 모든 연산도 역시 Error 예외를 만들어내요.

<?php

class Test {
    public function __construct(
        public readonly int $i = 0,
        public readonly array $ary = [],
    ) {}
}

$test = new Test;
$test->i += 1;
$test->i++;
++$test->i;
$test->ary[] = 1;
$test->ary[0][] = 1;
unset($test->ary[0]);
$ref =& $test->i;
$test->i =& $ref;
byRef($test->i);
foreach ($test as &$prop);
?>

하지만 readonly 속성이 내부 가변성(interior mutability)까지 막아주는 건 아니에요. readonly 속성에 들어 있는 객체(또는 리소스) 자체는 내부적으로 여전히 수정할 수 있어요.

<?php

class Test {
    public function __construct(public readonly object $obj) {}
}

$test = new Test(new stdClass);
// Legal interior mutation.
$test->obj->foo = 1;
// Illegal reassignment.
$test->obj = new stdClass;
?>

PHP 8.3.0부터는 객체를 복제(clone)할 때 __clone() 메서드 안에서 readonly 속성을 다시 초기화할 수 있어요.

예제 #6 — 읽기 전용 속성과 복제

<?php
class Test1 {
    public readonly ?string $prop;

    public function __clone() {
        $this->prop = null;
    }

    public function setProp(string $prop): void {
        $this->prop = $prop;
    }
}

$test1 = new Test1;
$test1->setProp('foobar');

$test2 = clone $test1;
var_dump($test2->prop); // NULL
?>

Note:

PHP 8.4.0부터는 __clone() 안에서 readonly 속성을 간접적으로 수정하는 건 더 이상 허용되지 않아요. 예를 들어 $ref = &$this->prop처럼 참조를 얻는 게 그런 경우인데, readonly 초기화 중에도 이미 금지되어 있던 방식이에요.

동적 속성 (Dynamic properties)

객체에 존재하지 않는 속성에 값을 대입하려고 하면, PHP는 알아서 그에 해당하는 속성을 만들어내요. 이렇게 동적으로 만들어진 속성은 그 클래스 인스턴스에서만 쓸 수 있어요.

⚠️ 경고

동적 속성은 PHP 8.2.0부터 deprecated 처리됐어요. 속성을 직접 선언하는 걸 권장해요. 임의의 속성 이름을 다뤄야 한다면 클래스가 매직 메서드인 __get()__set()을 구현하면 돼요. 마지막 수단으로 클래스에 #[\\AllowDynamicProperties] 속성을 붙이는 방법도 있어요.