0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PHP7.4のクラス継承で注意すべきこと:バグなのか、仕様なのか?

Last updated at Posted at 2022-06-20
../

PHP7.4で継承を使っていると、アクセス修飾子でおかしな挙動がある。バグなのか、仕様なのか分からないが、注意が必要である。

Userクラス単独でのテスト

まず、Userというクラスを定義してみる。idとnameを属性に持たせる。

<?php
namespace samples;
class User {

  private $id;
  private $name;      // ここをprotected,publicに変えてテスト
  
  public function __construct(string $id, string $name) {
    $this->setId($id);
    $this->setName($name);
  }
  public function getId(): string { return $this->id; }
  public function getName(): string { return $this->name; }
  public function setId(string $id): void { $this->id = $id; }
  public function setName(string $name): void { $this->name = $name; }
}

以下のようなテストコードを実行してみる。name属性の変更を試みる。

$user = new User('U001', 'Aさん');
$user->name = '山本さん';     // private,protectedだと、ここでエラー
$user->setName('池田さん');
var_dump($user);

すると、privateな属性に代入しようとしているのでエラーになる。これは正しい動作である。

Fatal error: Uncaught Error: Cannot access private property 
samples\User::$name in samples\User.php

ちなみに、宣言をprotectedに変えても同様なエラーが出る。そして、publicに変えると正常に動作して、以下のようなvar_dump()の結果が表示される。一旦、山本さんに変更され、その後で池田さんに上書きされる。正しい挙動である。

class samples\User#1 (2) {
  public $id =>
  string(4) "U001"
  public $name =>
  string(12) "池田さん"
}

ここまでは問題ない。

Identifierを継承したUserクラスでのテスト

次に、Identifierというクラスとそれを継承したUserクラスで試してみる。
まず、Identifierというクラスを作る。前述のUserをリネームして、abstractを宣言したものである。

<?php
namespace samples;

abstract class Identifier {

  private $id;
  private $name;
  
  public function __construct(string $id, string $name) {
    $this->setId($id);
    $this->setName($name);
  }
  public function getId(): string { return $this->id; }
  public function getName(): string { return $this->name; }
  public function setId(string $id): void { $this->id = $id; }
  public function setName(string $name): void { $this->name = $name; }
}

次に、Identifierを継承したUserクラスを作る。継承をイメージしやすいようにmail属性を追加してみる。

<?php
namespace samples; 
require_once(__DIR__."/Identifier.php");

class User extends Identifier {

  private $mail;
  
  public function __construct(string $id, string $name, string $mail){
    parent::__construct($id, $name);
    $this->setMail($mail); 
  }
  public function getMail(): string { return $this->mail; }
  public function setMail(string $mail): void { $this->mail = $mail; }
}

この状況で、前述のテストコードと同等のことを行ってみる。

$user = new User('U001', 'Aさん', "aaa@xxx.com");
$user->name = '山本さん';
$user->setName('池田さん');
var_dump($user);

結果は、以下のようになる。エラーにはならない。

class samples\User#1 (4) {
  private $mail =>
  string(11) "aaa@xxx.com"
  private $id =>
  string(4) "U001"
  private $name =>
  string(12) "池田さん"
  public $name =>
  string(12) "山本さん"
}

$user->name = '山本さん';」の実行では、privateな属性なのでアクセスできないかと思えば、勝手に新たなpublicな$nameという属性を追加しているのである。継承元を探索できていない。また、おそらく、PHPでのクラスの実装はarrayを使っていて、アクセス修飾子+属性名で識別しているのだろう。PHPのバグなのか、仕様なのかは分からない。

PHPでクラスを扱うときは、アクセス修飾子を信用せずに、setter/getterをきちんと定義して使用しないと危険である。

../
0
0
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?