객체가 new로 만들어질 때 가장 먼저 호출되는 초기화 메소드입니다.
시그니처: public function __construct(/* ... */) { /* 초기화 */ }
반환값이 없습니다. 값을 반환하려고 하면 오류입니다(필요하면 예외를 던져 인스턴스 생성을 막을 수 있음).
class User {
private int $id;
private string $name;
public function __construct(int $id, string $name) {
if ($id <= 0) throw new InvalidArgumentException('id > 0');
$this->id = $id;
$this->name = $name;
}
}
$u = new User(1, 'Kim');
생성자 매개변수에 가시성 + 타입을 붙이면 선언과 대입이 한 번에 됩니다.
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; }
}
$p = new Point(10, 20);
승격과 검증 로직을 함께 쓰려면 본문에서 가드하면 됩니다(승격된 프로퍼티는 본문 진입 전에 이미 대입됨).
class Email {
public function __construct(public string $value) {
if (!filter_var($this->value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('invalid email');
}
}
}
생성자에서만 대입 가능, 그 이후 불변.
final class Money {
public function __construct(
public readonly int $amount,
public readonly string $currency
) {}
}
타입이 달린 프로퍼티는 초기화 전에 접근하면 오류가 납니다.
기본값은 상수 표현식만 가능(함수 호출/new 불가) → 이런 초기화는 생성자에서 처리.
class Session {
private string $token; // 반드시 생성자에서 채워야 함
public function __construct() {
$this->token = bin2hex(random_bytes(16));
}
}
자식이 생성자를 재정의하면, 부모 생성자는 자동 호출되지 않습니다. 필요하면 직접 호출하세요.
class Base {
public function __construct(protected PDO $db) {}
}
class Repo extends Base {
public function __construct(PDO $db, private string $table) {
parent::__construct($db); // 반드시 명시 호출
}
}
self vs static은 생성자 내부보다는 팩토리 메소드에서 차이가 큼(아래 #7 참조).
PHP엔 메소드 **오버로딩(같은 이름 다른 시그니처)**이 없습니다. 대안:
기본값과 nullable/union 타입 사용
이름 있는 정적 팩토리(fromString(), fromArray() 등) 제공
가변 인자 ...$args로 직접 분기(가독성 떨어질 수 있음)
class Color {
private function __construct(private int $r, private int $g, private int $b) {}
public static function fromRGB(int $r, int $g, int $b): self { return new self($r,$g,$b); }
public static function fromHex(string $hex): self {
$hex = ltrim($hex, '#');
return new self(hexdec(substr($hex,0,2)), hexdec(substr($hex,2,2)), hexdec(substr($hex,4,2)));
}
}
직접 인스턴스화 금지하고, 정적 팩토리만 허용하고 싶을 때.
final class Token {
private function __construct(private string $value) {}
public static function random(): self { return new self(bin2hex(random_bytes(16))); }
public static function fromString(string $v): self { return new self($v); }
}
상속 고려 시 팩토리에서 new static()을 쓰면 늦은 정적 바인딩으로 하위 타입 반환이 유연합니다.
외부 자원(Pdo, 클라이언트, 서비스 등)은 생성자 주입이 가장 단순하고 테스트하기 좋습니다.
테스트에서 가짜 구현(인터페이스) 주입이 쉬워집니다.
interface Clock { public function now(): DateTimeImmutable; }
final class SystemClock implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable(); } }
final class Greeter {
public function __construct(private Clock $clock) {}
public function greet(string $name): string {
$h = (int)$this->clock->now()->format('G');
return ($h < 12 ? 'Good morning' : 'Hello').", $name";
}
}
class ReportQuery {
public function __construct(
private DateTimeImmutable $from,
private DateTimeImmutable $to,
private int $limit = 100
) {}
}
$q = new ReportQuery(
from: new DateTimeImmutable('2025-08-01'),
to: new DateTimeImmutable('2025-08-31'),
limit: 1000
);
생성자에서 불변식 위반은 즉시 예외로 막으세요(잘못된 객체가 태어나지 않도록).
여러 외부 작업을 한다면 실패 시 부분 상태가 남지 않게 설계(가능하면 생성자에서 무거운 I/O는 피하고, 별도 init()/connect() 메소드로 분리).
class Account {
public function __construct(private int $id, private int $balance = 0) {
if ($id <= 0) throw new InvalidArgumentException('id > 0');
if ($balance < 0) throw new InvalidArgumentException('balance >= 0');
}
}
try { $a = new Account(-1); } catch (InvalidArgumentException $e) { /* 처리 */ }
트레이트도 __construct를 가질 수 있습니다. 클래스와 충돌하면 클래스 쪽이 우선.
필요하다면 별칭으로 살려서 수동 호출합니다.
trait T {
public function __construct(private string $x) {}
}
class C {
use T { __construct as private tConstruct; }
public function __construct() { $this->tConstruct('v'); }
}
익명 클래스도 생성자를 가질 수 있습니다.
$o = new class(10) {
public function __construct(public int $n) {}
};
Enum은 생성자가 제한적입니다(케이스 정의와 함께 백킹/메소드 제공). 일반 객체처럼 자유로운 상태 주입은 아님.
unserialize()는 생성자를 호출하지 않습니다. 복원 시 초기화가 필요하면 __wakeup()/__unserialize() 활용.
리플렉션의 newInstanceWithoutConstructor()로 생성자 없이 객체 만들기가 가능하지만 특수 상황에서만 사용하세요(테스트/프록시 등).
무거운 I/O(DB 연결, 원격 호출)를 생성자에서 직접 수행 → 실패/지연 시 인스턴스화 자체가 깨짐. 가벼운 검증까지만 하고 실행은 별도 메소드로 분리 권장.
생성자에서 오버라이드 가능한 메소드 호출 → 자식에서 덮어쓴 메소드가 부분 초기화 상태로 실행돼 버그 유발.
불변 설계에서 public 프로퍼티 노출 → 불변식 붕괴. readonly/메소드로 노출.
동적 프로퍼티 사용(PHP 8.2+ 비권장). 반드시 명시 선언.
final class Email {
public function __construct(public readonly string $value) {
if (!filter_var($this->value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('invalid email');
}
}
}
final class User {
public function __construct(
private int $id,
private Email $email,
private DateTimeImmutable $createdAt = new DateTimeImmutable()
) {
if ($this->id <= 0) throw new InvalidArgumentException('id > 0');
}
public function id(): int { return $this->id; }
public function email(): Email { return $this->email; }
}