PHP クラスの静的メンバ挙動を通して諸々覚える

ダブルコロンのスコープ定義演算子

$foo::BAR とか Foo::$bar とか
PHPのコロンふたつの記法、なんとなく得体がしれずスルーし続けてきたが、自作クラスに定数を定義した際はじめて、定数や静的メンバの呼び出しに用いるものと知る。
インスタンスメンバへのアクセスにつかう -> アロー演算子 では定数を呼び出せないことから気づいた。

PHP: スコープ定義演算子 (::) - Manual

static を用いたクラスの検証

そもそもオブジェクト指向におけるstatic修飾子の効果や静的メンバの概念が曖昧だった。
(jsなどでは定義済みの静的メンバを頻繁かつ無意識に使用していたけど) 自分で定義してみることでようやくしっかり把握できました。

デバッグついでに、あまり使ったことのなかったマジック定数の出力結果も確認。 さらにマジック定数の変化を知るために traitnamespace の使い方も確認。

わからないことが掘れば掘るほど芋づる式にでてきてキリがない。 あまりPHPで凝った処理書いたことがないからなあ…。

トレイト

使ったことないどころか今日初めて知った単語。
処理をまとめられる、sassのmixinのようなもの。 名前空間とかトレイトとか、がっっつりPHP組まなきゃならない場合にうまく使えたら便利そうだけど、 下手するとかえってややこしくなりそう。

<?php
trait classOutputTest
{
  static function getClassname()
  {
    return __CLASS__;
  }
  // static $msg = '';
  static $counter = 0;
  static $msg_in_trait = '👻 $msg_in_trait in <span style="color:#fff;background:blue;">' . __CLASS__ . '</span>';
  static function getMsgInTrait()
  {
    return '⛄ getMsgInTrait() in <span style="color:#fff;background:red;">' . __CLASS__ . '</span>';
  }
}

クラス

手抜きしてstatic宣言にトレイト classOutputTest を使いまわす。(これにより予期せぬ結果となったので後述)

<?php
class Bird
{
  function __construct()
  {
    static::outputTest();
  }
  static function outputTest($classname=__CLASS__)
  {
    echo "==================================" . PHP_EOL;
    if (property_exists(__CLASS__, 'msg')) {
      echo '<strong> ' . static::$msg . '</strong>' . PHP_EOL;
    }
    echo 'Bird & Birdを継承したインスタンス数: ' . ++self::$counter . PHP_EOL;
    echo '__CLASS__ ➡ ' . __CLASS__ . PHP_EOL;
    echo '$classname ➡ ' . $classname . PHP_EOL;
    echo '__TRAIT__ ➡ ' . __TRAIT__ . PHP_EOL;
    echo '__METHOD__ ➡ ' . __METHOD__ . PHP_EOL;
    echo '__FUNCTION__ ➡ ' . __FUNCTION__ . PHP_EOL;
    echo '__NAMESPACE__(クラス直下関数に記述) ➡ ' . static::getNamespace() . PHP_EOL;
    echo '__NAMESPACE__(trait内に記述) ➡ ' . __NAMESPACE__ . PHP_EOL;
    echo PHP_EOL.'⚙ 以下トレイト classOutputTest で全クラス一括定義した静的メンバ ⚙'.PHP_EOL;
    echo 'static::getMsgInTrait()  ➡ ' . static::getMsgInTrait() . PHP_EOL;
    echo 'static::$msg_in_trait ➡ ' . static::$msg_in_trait . PHP_EOL;
  }
  
  static function getNamespace()
  {
    return __NAMESPACE__;
  }
  use classOutputTest;
  static $msg = "🐣 大元となる [Birdクラス]";
}

class Chicken extends Bird
{
  use classOutputTest;
  static $msg = "🐓 Birdを継承 [Chickenクラス]";
}

namespaceの概念理解用に、名前空間を新規に作成してBirdを継承したPenguin,Kiwiクラスを作成

<?php
// # namespace :すべての処理にさきがけて宣言する必要がある
namespace TOBENAI_TORI;

trait tobenaiTrait{
  function __construct()
  {
    static::outputTest(__CLASS__);
    echo PHP_EOL.'⚙ 名前空間 ['.__NAMESPACE__.'] 内のトレイト tobenaiTrait によるコンストラクタ追加処理 ⚙'.PHP_EOL;
    echo '__TRAIT__ ➡ ' . __TRAIT__ . PHP_EOL;
    echo '__METHOD__ ➡ ' . __METHOD__ . PHP_EOL;
    echo '__FUNCTION__ ➡ ' . __FUNCTION__ . PHP_EOL;
    echo '__NAMESPACE__ ➡ ' . __NAMESPACE__ . PHP_EOL;

  }
  static function getNamespace(){//ここで親クラスの処理上書きすると名前空間が出力される(なくすと空に)
    return __NAMESPACE__;
  }
}

class Penguin extends \Bird // \でグローバル名前空間を指定
{
  use \classOutputTest, tobenaiTrait;

  static $msg ="🐧 名前空間 [TOBENAI_TORI] 、グローバル空間のBirdを継承した [Penguinクラス] ";
}
class Kiwi extends \Bird // \でグローバル名前空間を指定
{
  use \classOutputTest, tobenaiTrait;

  static $msg ="🥝 名前空間 [TOBENAI_TORI] 、グローバル空間のBirdを継承した [Kiwiクラス] ";
}

これらを全部読み込んで実行

<?php
    $bird = new Bird();
    $chicken = new Chicken();
    $ns_penguin = '\TOBENAI_TORI\Penguin';
    $tobenai_peguin = new $ns_penguin();
    $ns_kiwi = '\TOBENAI_TORI\Kiwi';
    $tobenai_kiwi  = new $ns_kiwi();

名前空間名のみを変数化してクラス名と足したかったけど、どう書けばいいのかわからなかった。

出力結果

==================================
 🐣 大元となる [Birdクラス]
Bird & Birdを継承したインスタンス数: 1
__CLASS__ ➡ Bird
$classname ➡ Bird
__TRAIT__ ➡ 
__METHOD__ ➡ Bird::outputTest
__FUNCTION__ ➡ outputTest
__NAMESPACE__(クラス直下関数に記述) ➡ 
__NAMESPACE__(trait内に記述) ➡ 

⚙ 以下トレイト classOutputTest で全クラス一括定義した静的メンバ ⚙
static::getMsgInTrait()  ➡ ⛄ getMsgInTrait() in Bird
static::$msg_in_trait ➡ 👻 $msg_in_trait in Bird
==================================
 🐓 Birdを継承 [Chickenクラス]
Bird & Birdを継承したインスタンス数: 2
__CLASS__ ➡ Bird
$classname ➡ Bird
__TRAIT__ ➡ 
__METHOD__ ➡ Bird::outputTest
__FUNCTION__ ➡ outputTest
__NAMESPACE__(クラス直下関数に記述) ➡ 
__NAMESPACE__(trait内に記述) ➡ 

⚙ 以下トレイト classOutputTest で全クラス一括定義した静的メンバ ⚙
static::getMsgInTrait()  ➡ ⛄ getMsgInTrait() in Chicken
static::$msg_in_trait ➡ 👻 $msg_in_trait in Bird
==================================
 🐧 名前空間 [TOBENAI_TORI] 、グローバル名前空間のBirdを継承した [Penguinクラス] 
Bird & Birdを継承したインスタンス数: 3
__CLASS__ ➡ Bird
$classname ➡ TOBENAI_TORI\Penguin
__TRAIT__ ➡ 
__METHOD__ ➡ Bird::outputTest
__FUNCTION__ ➡ outputTest
__NAMESPACE__(クラス直下関数に記述) ➡ TOBENAI_TORI
__NAMESPACE__(trait内に記述) ➡ 

⚙ 以下トレイト classOutputTest で全クラス一括定義した静的メンバ ⚙
static::getMsgInTrait()  ➡ ⛄ getMsgInTrait() in TOBENAI_TORI\Penguin
static::$msg_in_trait ➡ 👻 $msg_in_trait in Bird

⚙ 名前空間 [TOBENAI_TORI] 内のトレイト tobenaiTrait によるコンストラクタ追加処理 ⚙
__TRAIT__ ➡ TOBENAI_TORI\tobenaiTrait
__METHOD__ ➡ TOBENAI_TORI\tobenaiTrait::__construct
__FUNCTION__ ➡ __construct
__NAMESPACE__ ➡ TOBENAI_TORI
==================================
 🥝 名前空間 [TOBENAI_TORI] 、グローバル名前空間のBirdを継承した [Kiwiクラス] 
Bird & Birdを継承したインスタンス数: 4
__CLASS__ ➡ Bird
$classname ➡ TOBENAI_TORI\Kiwi
__TRAIT__ ➡ 
__METHOD__ ➡ Bird::outputTest
__FUNCTION__ ➡ outputTest
__NAMESPACE__(クラス直下関数に記述) ➡ TOBENAI_TORI
__NAMESPACE__(trait内に記述) ➡ 

⚙ 以下トレイト classOutputTest で全クラス一括定義した静的メンバ ⚙
static::getMsgInTrait()  ➡ ⛄ getMsgInTrait() in TOBENAI_TORI\Kiwi
static::$msg_in_trait ➡ 👻 $msg_in_trait in Bird

⚙ 名前空間 [TOBENAI_TORI] 内のトレイト tobenaiTrait によるコンストラクタ追加処理 ⚙
__TRAIT__ ➡ TOBENAI_TORI\tobenaiTrait
__METHOD__ ➡ TOBENAI_TORI\tobenaiTrait::__construct
__FUNCTION__ ➡ __construct
__NAMESPACE__ ➡ TOBENAI_TORI

まとめ

  • グローバル空間は\で指定可能
  • __NAMESPACE__namespace hoge; と同一ファイル上で出力するとその名前空間名が出力される。
    たとえば任意の名前空間配下で関数を宣言し、中身だけ別ファイルにしたとする。別ファイル上で__NAMESPACE__を出力すると空っぽになる。
  • 子クラスで静的メンバを上書きして参照したい場合は self::$hoge でなく static::$hoge。selfだと最初に定義したクラスのメンバが参照される。
  • クラスメンバの存在チェック property_exists(__CLASS__, 'msg')

予期せぬ現象 (子クラスの静的メンバをクラス直下にきちんと書けば問題ないようなんだけども)

親クラス・子クラスともにトレイトで静的プロパティ一括定義したところ、気になる現象がおきた。

  • トレイト内で宣言した静的プロパティ($msg_in_trait)が、 static::$msg_in_trait で呼び出しているにもかかわらずすべて親クラスの内容に統一される
  • 同じくトレイト内で宣言している関数については、それぞれのクラスごとの内容で実行される

参考

むず…