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] 속성을 붙이는 방법도 있어요.