IMG-LOGO
공지사항 :

PHP에서 생성자(__construct)

lmkfox - 2025-08-27 07:07:06 25 Views 0 Comment

1) 생성자란?

  • 객체가 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');

2) 생성자 프로퍼티 승격(PHP 8.0+)

생성자 매개변수에 가시성 + 타입을 붙이면 선언과 대입이 한 번에 됩니다.

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');
        }
    }
}

3) readonly 프로퍼티(PHP 8.1+)

  • 생성자에서만 대입 가능, 그 이후 불변.

final class Money {
    public function __construct(
        public readonly int $amount,
        public readonly string $currency
    ) {}
}

4) 타입드 프로퍼티 초기화 규칙

  • 타입이 달린 프로퍼티는 초기화 전에 접근하면 오류가 납니다.

  • 기본값은 상수 표현식만 가능(함수 호출/new 불가) → 이런 초기화는 생성자에서 처리.

class Session {
    private string $token;          // 반드시 생성자에서 채워야 함
    public function __construct() {
        $this->token = bin2hex(random_bytes(16));
    }
}

5) 상속과 

parent::__construct()

  • 자식이 생성자를 재정의하면, 부모 생성자는 자동 호출되지 않습니다. 필요하면 직접 호출하세요.

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 참조).

6) 오버로딩은 없고, 선택지는 3가지

PHP엔 메소드 **오버로딩(같은 이름 다른 시그니처)**이 없습니다. 대안:

  1. 기본값nullable/union 타입 사용

  2. 이름 있는 정적 팩토리(fromString(), fromArray() 등) 제공

  3. 가변 인자 ...$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)));
    }
}

7) private 생성자와 팩토리 패턴

  • 직접 인스턴스화 금지하고, 정적 팩토리만 허용하고 싶을 때.

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()을 쓰면 늦은 정적 바인딩으로 하위 타입 반환이 유연합니다.

8) DI 친화적 생성자

  • 외부 자원(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";
    }
}

9) 호출 편의: 기본값·이름 있는 인자·가변 인자

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
);

10) 예외와 트랜잭션적 초기화

  • 생성자에서 불변식 위반은 즉시 예외로 막으세요(잘못된 객체가 태어나지 않도록).

  • 여러 외부 작업을 한다면 실패 시 부분 상태가 남지 않게 설계(가능하면 생성자에서 무거운 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) { /* 처리 */ }

11) 트레이트와 생성자

  • 트레이트도 __construct를 가질 수 있습니다. 클래스와 충돌하면 클래스 쪽이 우선.

  • 필요하다면 별칭으로 살려서 수동 호출합니다.

trait T {
    public function __construct(private string $x) {}
}
class C {
    use T { __construct as private tConstruct; }
    public function __construct() { $this->tConstruct('v'); }
}

12) 익명 클래스/열거형과 생성자

  • 익명 클래스도 생성자를 가질 수 있습니다.

$o = new class(10) {
    public function __construct(public int $n) {}
};

  • Enum은 생성자가 제한적입니다(케이스 정의와 함께 백킹/메소드 제공). 일반 객체처럼 자유로운 상태 주입은 아님.

13) 생성자 우회/직렬화 관련

  • unserialize()는 생성자를 호출하지 않습니다. 복원 시 초기화가 필요하면 __wakeup()/__unserialize() 활용.

  • 리플렉션의 newInstanceWithoutConstructor()로 생성자 없이 객체 만들기가 가능하지만 특수 상황에서만 사용하세요(테스트/프록시 등).

14) 안티패턴과 주의점

  • 무거운 I/O(DB 연결, 원격 호출)를 생성자에서 직접 수행 → 실패/지연 시 인스턴스화 자체가 깨짐. 가벼운 검증까지만 하고 실행은 별도 메소드로 분리 권장.

  • 생성자에서 오버라이드 가능한 메소드 호출 → 자식에서 덮어쓴 메소드가 부분 초기화 상태로 실행돼 버그 유발.

  • 불변 설계에서 public 프로퍼티 노출 → 불변식 붕괴. readonly/메소드로 노출.

  • 동적 프로퍼티 사용(PHP 8.2+ 비권장). 반드시 명시 선언.

15) 한 장 예제: 값 객체 + 엔티티 초기화

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; }
}

댓글