클래스의 각 객체가 개별적으로 보유하는 상태입니다.
문법적으로는 프로퍼티(property) 라고 부르며, static이 아닌 프로퍼티가 인스턴스 변수입니다.
반대로 static 프로퍼티는 클래스 전체가 공유합니다(인스턴스 변수 아님).
class Counter {
// 인스턴스 변수(객체마다 따로 있음)
private int $value = 0;
public function inc(int $step = 1): void { $this->value += $step; }
public function value(): int { return $this->value; }
}
$a = new Counter();
$b = new Counter();
$a->inc(2);
echo $a->value(); // 2
echo $b->value(); // 0 ← 서로 다른 상태
class User {
public int $id; // 어디서나 접근 가능(권장 X)
protected string $role; // 자기 자신 + 자식 클래스에서 접근
private string $name; // 해당 클래스 내부에서만 접근
public function __construct(int $id, string $name, string $role = 'user') {
$this->id = $id;
$this->name = $name;
$this->role = $role;
}
public function name(): string { return $this->name; } // 게터
}
기본 권장: 프로퍼티는 private, 읽기/변경은 메소드로 캡슐화.
외부 직접 접근이 꼭 필요할 때만 public을 신중히 사용.
PHP 7.4+부터 타입드 프로퍼티를 지원합니다.
class Product {
public int $id;
public string $name;
public ?string $description = null; // nullable
public int|float $price; // union 타입(8.0+)
public function __construct(int $id, string $name, int|float $price) {
$this->id = $id;
$this->name = $name;
$this->price = $price;
}
}
타입이 있는 프로퍼티는 초기화 전에 접근하면 오류가 납니다. 생성자에서 반드시 채우세요.
프로퍼티 기본값은 상수 표현식만 가능(객체 생성 등은 안 됨) → 생성자에서 처리.
class Point {
private int $x;
private int $y;
public function __construct(int $x, int $y) {
$this->x = $x;
$this->y = $y;
}
}
생성자 매개변수에 접근 제어자와 타입을 붙여 선언+초기화를 한 줄로 처리합니다.
class Point {
public function __construct(
private int $x,
private int $y,
) {}
public function x(): int { return $this->x; }
public function y(): int { return $this->y; }
}
class Session {
private ?string $token = null;
public function token(): string {
if ($this->token === null) {
$this->token = bin2hex(random_bytes(16));
}
return $this->token;
}
}
생성 이후 수정 불가한 불변 상태가 필요할 때 사용합니다.
final class Money {
public function __construct(
public readonly int $amount,
public readonly string $currency
) {}
}
$m = new Money(1000, 'KRW');
// $m->amount = 2000; // 오류: readonly는 재할당 불가
클래스 내부: $this->prop
외부: $obj->prop (단, public이어야 함)
보통은 메소드로 캡슐화합니다.
class Profile {
private string $nickname;
public function __construct(string $nickname) {
$this->nickname = $nickname;
}
public function rename(string $new): void {
if ($new === '') throw new InvalidArgumentException('empty');
$this->nickname = $new;
}
public function nickname(): string { return $this->nickname; }
}
$p = new Profile('kim');
$p->rename('lee');
echo $p->nickname(); // lee
객체가 null일 수 있을 때 안전하게 프로퍼티/메소드에 접근합니다.
$city = $user?->address?->city; // 중간이 null이면 전체가 null
예전엔 선언하지 않은 프로퍼티를 런타임에 추가해도 됐지만, PHP 8.2+에서 폐지(Deprecated) 경고가 발생합니다.
꼭 필요하면 #[\AllowDynamicProperties] 또는 stdClass 활용, 권장 대안은 명시적 선언 혹은 __get/__set 매직 메소드.
class Box {
private array $data = [];
public function __get(string $name) { return $this->data[$name] ?? null; }
public function __set(string $name, $value) { $this->data[$name] = $value; }
}
class Example {
public static int $count = 0; // 모든 인스턴스가 공유
public function __construct(private int $id) {
self::$count++;
}
}
$a = new Example(1);
$b = new Example(2);
echo Example::$count; // 2
인스턴스 변수: 각 객체가 독립 보유
static 변수: 클래스 단위로 공유
상태를 공유해야 하는 특수한 경우가 아니라면 static 남용 금지.
부모의 protected 프로퍼티는 자식에서 사용 가능.
같은 이름의 프로퍼티를 재선언하는 일은 피하세요(혼동과 호환성 이슈 유발).
공통 상태는 부모에 protected로 두고, 동작은 자식에서 확장하는 구조가 안전합니다.
PHP 언어 차원의 제네릭은 없지만, DocBlock으로 의도를 명시합니다.
/** @var list<User> */
private array $members = [];
/** @param list<User> $members */
public function __construct(array $members = []) { $this->members = $members; }
정적 분석기(phpstan/psalm)와 IDE가 이 정보를 활용합니다.
final class Email {
public function __construct(public readonly string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('invalid email');
}
}
}
final class Account {
public function __construct(
private int $id,
private Email $email,
private DateTimeImmutable $createdAt = new DateTimeImmutable()
) {}
public function changeEmail(Email $email): void {
$this->email = $email; // 도메인 규칙 검증은 Email에서 담당
}
public function id(): int { return $this->id; }
public function email(): Email { return $this->email; }
public function createdAt(): DateTimeImmutable { return $this->createdAt; }
}
포인트
**값 객체(Email)**는 readonly로 불변.
**엔티티(Account)**는 도메인 규칙을 따르며 인스턴스 변수를 통해 상태 변화를 관리.
타입드 프로퍼티를 초기화 전에 접근 → 오류. 생성자/팩토리에서 반드시 채우기.
선언 없이 $obj->foo = ... 추가(동적 프로퍼티) → 8.2+에서 경고. 반드시 선언.
외부에서 public 프로퍼티를 직접 바꿈 → 불변식 깨짐. 메소드로 검증/수정.
프로퍼티 기본값에 new나 함수 호출 사용 → 불가. 생성자에서 처리.
공유 상태가 필요한 게 아닌데 static 사용 → 테스트/동시성 문제 유발.
인스턴스 변수는 객체마다 독립된 상태를 담는 프로퍼티입니다.
private + 타입 선언 + 생성자 초기화가 기본기.
변경을 통제하고 싶다면 게터/세터 대신 의미 있는 도메인 메소드를 제공하세요.
불변이 적합하면 readonly를, 동적 필드가 필요하면 명시적 선언 또는 매직 메소드로.