タイトル: コンストラクタ
SEOタイトル: PHP コンストラクタ完全ガイド (PHP 8 Constructor Property Promotion / DI)
| この記事の要点 |
|
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
コンストラクタでやるべきこと / べきでないこと
| OK | NG (避けるべき) |
|---|---|
| 必須プロパティの代入 | 重い処理 (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。むしろ推奨。不正な引数で半端な状態のオブジェクトを作るくらいなら例外で止めるべき。