15.

PHP コンストラクタ完全ガイド (PHP 8 Constructor Property Promotion / DI)

編集
この記事の要点
  • PHP のコンストラクタは __construct(...)new ClassName(args) で自動呼び出し
  • 親クラスのコンストラクタは parent::__construct(...) で明示的に呼ぶ
  • PHP 8 以降は Constructor Property Promotion でフィールド宣言と代入を 1 行に
  • __destruct はオブジェクト破棄時に呼ばれる (循環参照や fclose リソース後始末用)
  • コンストラクタは 依存注入 (DI) に最も使われる場所。Laravel/Symfony は自動解決

PHP のコンストラクタ基本

class User {
    public string $name;
    public int $age;

    // コンストラクタ (new した瞬間に呼ばれる)
    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

// 利用
$user = new User('Taro', 30);
echo $user->name;   // Taro

PHP 8: Constructor Property Promotion

PHP 8.0 で導入された記法。フィールド宣言と代入を 1 行に書けます:

// 従来 (PHP 7)
class User {
    public string $name;
    public int $age;
    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

// PHP 8.0+ Property Promotion
class User {
    public function __construct(
        public string $name,
        public int $age,
        private ?string $email = null,
        public readonly int $id = 0,    // PHP 8.1+ readonly
    ) {}
}

$user = new User('Taro', 30);
echo $user->name;     // Taro
echo $user->age;      // 30

親クラスのコンストラクタ呼び出し

class Animal {
    public function __construct(public string $name) {
        echo "Animal created: $name\n";
    }
}

class Dog extends Animal {
    public function __construct(string $name, public string $breed) {
        // ★ 親のコンストラクタを明示的に呼ぶ
        parent::__construct($name);
        echo "Dog created: $breed\n";
    }
}

$d = new Dog('Pochi', 'Shiba');
// Animal created: Pochi
// Dog created: Shiba

注意: PHP は親クラスのコンストラクタを自動では呼びません。必要なら明示的に parent::__construct(...)

デストラクタ

class FileLogger {
    private $fp;

    public function __construct(string $path) {
        $this->fp = fopen($path, 'a');
    }

    public function log(string $msg): void {
        fwrite($this->fp, "$msg\n");
    }

    // オブジェクトが破棄されるとき呼ばれる
    public function __destruct() {
        if ($this->fp) {
            fclose($this->fp);
        }
    }
}

$logger = new FileLogger('/tmp/app.log');
$logger->log('start');
// スクリプト終了時に __destruct が呼ばれて fclose される

依存注入 (DI) の典型例

interface Mailer {
    public function send(string $to, string $body): void;
}

class SmtpMailer implements Mailer {
    public function send(string $to, string $body): void { /* ... */ }
}

class UserRegistrar {
    // コンストラクタで依存を受け取る (Constructor Injection)
    public function __construct(
        private Mailer $mailer,
        private LoggerInterface $logger,
    ) {}

    public function register(string $email): void {
        $this->logger->info("Registering $email");
        $this->mailer->send($email, 'Welcome!');
    }
}

// 利用 (手動)
$service = new UserRegistrar(new SmtpMailer(), new FileLogger());

// Laravel コンテナなら自動解決
$service = app(UserRegistrar::class);

static factory method

コンストラクタの代わりに「意図が明確な名前付きファクトリ」を使うパターン:

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

    public static function yen(int $amount): self {
        return new self($amount, 'JPY');
    }

    public static function dollar(int $amount): self {
        return new self($amount, 'USD');
    }

    public function format(): string {
        return "{$this->amount} {$this->currency}";
    }
}

$price = Money::yen(1000);
echo $price->format();   // 1000 JPY

Singleton: private コンストラクタ

class DatabaseConnection {
    private static ?self $instance = null;

    // 外部から new させない
    private function __construct(
        private string $host,
        private string $user
    ) {}

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self(
                $_ENV['DB_HOST'],
                $_ENV['DB_USER']
            );
        }
        return self::$instance;
    }
}

$db = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();
var_dump($db === $db2);   // true

コンストラクタでやるべきこと / べきでないこと

OKNG (避けるべき)
必須プロパティの代入重い処理 (DB / HTTP)
不変条件の検証 (invariant)例外を投げて中途半端な状態
依存性の受け取り (DI)グローバル変数の参照
ロガーの保存I/O (ログ書き出し等)

不変条件の検証

class Email {
    public function __construct(public readonly string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $value");
        }
    }
}

// この時点で必ず有効な email
$email = new Email('foo@example.com');

// 不正な値は new した瞬間に例外
$bad = new Email('not-an-email');   // InvalidArgumentException

FAQ

Q: コンストラクタを 2 つ書ける?
A: PHP はオーバーロード不可。__construct は 1 つだけ。代わりに static factory method を用意します。

Q: 親と子の両方で __construct を書いたら?
A: 子の __construct が呼ばれます。親を呼びたいなら parent::__construct(...) を明示的に書きます。

Q: コンストラクタ内で throw しても大丈夫?
A: OK。むしろ推奨。不正な引数で半端な状態のオブジェクトを作るくらいなら例外で止めるべき。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. 変数
  3. 演算子
  4. 標準ライブラリ
  5. 外部ライブラリ
  6. 制御構文
  7. リスト(配列)
  8. タプル
  9. セット
  10. 辞書(dict)
  11. クラスとメソッド
  12. 継承の概念と必要性
  13. 継承の構文
  14. コンストラクタ
  15. cookieの値の設定と取得
  16. 例外処理
  17. 例外を文字列で出力する方法
  18. httpリクエスト(curl)をする方法
  19. Responseオブジェクトの中身の確認
  20. 変数が空かどうか判定する方法
  21. タイムゾーンの設定と現在日時の取得と文字列化
  22. シングルクォーテーションとダブルクォーテーションの違い

最近更新/作成されたページ