PHPポケモンも順調に開発が進んでいると思いきや、ふとした気づきが自分の理解力を思い知らせることとなった今日このごろです。
プログラミングは奥が深く、しっかりと段階を追って理解を進めていけば、「これ・・・便利やんけ!」ってなることがかなり多いということがわかります。
それでは、かの有名な黄色いネズミさん+大人気進化先が選べる超優秀ポケモンであるイーブイさん協力のもと「PHPのオブジェクト指向」について学習していきましょう。
オブジェクト定数とは
現在のPHPポケモン、そして初期の「ピカチュウで学ぶオブジェクト指向」では、ポケモンが持つパラメーターなどをプロパティとして格納してきました。
ですが、そもそも「そのクラスで固定された値(定数)」というもの自体が定義できないかと考えていたところ、普通に存在しました。
ポケモン番号は定数
以下のようなクラスを例に見てみましょう。
<?php
// ピカチュウ
class Pikachu
{
private $number = 25;
}
これが今までのPHPポケモンで実装していたピカチュウです。もちろん皆さんご存知の通り、ピカチュウの全国図鑑ナンバーは025ですね。この値は外部から書き換えられては困るため、private(protected)のアクセス修飾子をつけて管理をしていましたが、ピカチュウのオブジェクトからでは変更出来てしまうという点を考えると、この定義自体あまり意味を成していないようにも思えます。
<?php
// ピカチュウ
class Pikachu
{
private $number = 25;
public function __construct()
{
// 書き換え可能!?
$this->number = 1000;
}
}
ここで登場するのが、オブジェクト定数です。オブジェクト定数は変数ではなく固定の値を定義するものであり、ポケモンであれば図鑑ナンバーなどを設定する際に適しています。
<?php
// ピカチュウ
class Pikachu
{
public const NUMBER = 25;
}
定数の定義方法は、定数名の前にconstを付けることです。変数ではないので、ドルマークも不要です。
定数はプロパティとは異なるため、アクセス方法はアロー演算子ではなくスコープ定義演算子(::)を使ってアクセスします。
$pikachu = new Pikachu;
$pikachu::NUMBER; # 25が取得できる
定数の注目すべき点は、インスタンスが不要だということです。
プロパティは上記コードの1行目のようにインスタンス化(new)しなければ設定されませんが、定数はオブジェクトそのものに設定されている値のため、クラス名に対して直接アクセスが可能なのです。
Pikachu::NUMBER; # 25が取得できる
これにより、今までインスタンス化して取得していたポケモン番号などが直接クラス指定で取り出せるようになりました。PHPポケモンのように、DBを使わずクラス管理しているシステムにおいては大きな収穫です。
静的変数とは
定数はオブジェクトそのものに設定されている値だということがわかりました。ですが、中には場合によって値を変更したいものも存在しますね。そんな時には「静的変数」を使用します。
進化先は静的変数
進化先が複数いるポケモンの代表といえば、初代からも登場してピカブイシリーズの発売によって一気に主人公格へと進出した「イーブイ」です。
このポケモンは、初代では3種類、第2世代では追加2種類のポケモンに進化します。使用された石の種類により進化先が変更したり、現在の時間帯で進化先が変更になるため「定数」として用意するには無理がありますね。なので、ここでは静的変数を使うことで対応します。
今回はわかりやすさ重視で「エーフィー」と「ブラッキー」への進化分岐でクラスを作成しました。
<?php
// イーブイ
class Eievui
{
public const NUMBER = 131;
public static $evolve = '';
// 現在の時間帯に合わせて進化先が変わる(金銀シリーズ)
public function now_time($zone)
{
switch ($zone) {
// エーフィーへ進化
case '朝':
static::$evolve = 'エーフィー';
break;
// ブラッキーへ進化
case '夜':
static::$evolve = 'ブラッキー';
break;
}
}
}
静的変数の定義方法は、変数名の前にstaticをつけることです。静的変数へのアクセスは、constと同じようにスコープ定義演算子(::)を使い、インスタンス化しなくてもその値へアクセスすることが可能です。
通常時は決まった進化先を持っておらず、now_timeのメソッドが呼び出された際に、そのときの時間帯に合わせて値を変更しています。
そしてプロパティとの大きな違いは、値をインスタンス自身が保有しているわけではないという点です。
$eievui1 = new Eievui;
$eievui2 = new Eievui;
$eievui1->now_time('朝');
$eievui2->now_time('夜');
先程作成したイーブイをインスタンス化、それぞれに朝と夜の時間帯をセットしました。今までのPHPポケモンで行なっていたプロパティの書き換えであれば、$eievui1の進化先には「エーフィー」、$eievui2の進化先には「ブラッキー」がセットされているはずですね。
しかし、結果は以下のようになります。
$eievui1::$evolve; # ブラッキー
$eievui2::$evolve; # ブラッキー
静的変数はあくまで、オブジェクトに対して設定されているため、それぞれのインスタンスに対して値は格納されていません。
もちろん、PHPファイルそのものが書き換わるわけではないので、次のページを読み込むことで進化先は空に戻ります。
この静的変数を上手く使えば、PHPポケモンの「へんしん」などの技を使用した際に書き換えを懸念していたステータスなどを、静的変数として定義して書き換えることで上手く活用することができそうです。
親と子の関係
もし定数や静的変数が「親」と「子」それぞれに設定されていれば、どのようにしてアクセスができるのかを確認してみましょう。
PHPポケモンでもおなじみ、ピカチュウのクラスの親として「Pokemon」を読み込ませましょう。
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public const NUMBER = 0;
}
# 子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public const NUMBER = 25;
}
ピカチュウに親クラスをセットして、それぞれに図鑑ナンバーを持たせました。この図鑑ナンバーをそれぞれのクラスから取り出す方法を見ていきます。
staticは誰のもの?
まずはアクセス方法の1つ「static」を使います。これはイーブイのクラスでも使用していましたが、静的変数などオブジェクトそのものが持っている値に対してアクセスする際に使用していましたね。これを、それぞれのクラス内で呼び出してみましょう。
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public const NUMBER = 0;
public function __construct()
{
echo 'Pokemon:'.static::NUMBER;
}
}
# 子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public const NUMBER = 25;
public function __destruct()
{
echo 'Pikachu:'.static::NUMBER;
}
}
new Pikachu;
両方をconstructにすると子クラスが優先されてしまうので、親(Pokemon)はconstruct、子(Pikachu)ではdestructを使い、staticでNUMBERの定数にアクセスしてみました。
出力結果は以下の通りです。
Pokemon:25
Pikachu:25
new Pikachuでインスタンス化したオブジェクト内でstaticを使ったアクセスをすると、どちらも「Pikachu」に対してアクセスをしているようですね。
これで、親クラスで全ポケモンにそれぞれが持ったオブジェクト定数や静的変数にアクセスしたい場合は、staticを使えば良いということがわかりました。
では、もし子である「Pikachu」がNUMBERの定数を持っていなければどうでしょうか?
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public const NUMBER = 0;
public function __construct()
{
echo 'Pokemon:'.static::NUMBER;
}
}
子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public function __destruct()
{
echo 'Pikachu:'.static::NUMBER;
}
}
new Pikachu;
出力結果は以下の通りです。
Pokemon:0
Pikachu:0
ピカチュウにはNUMBERの定数が設定されていませんが、親クラスには設定されているため、ピカチュウクラス内でstaticを使ってもエラーは出ず呼び出すことができました。
もしデフォルトとしてポケモン自体に値が決まっており、ポケモンによっては上書きして値を持たせる場合に、この仕組みは使えそうですね。
では、逆のパターン(親に番号なし・子に番号有り)ではいかがでしょうか?
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public function __construct()
{
echo 'Pokemon:'.static::NUMBER;
}
}
# 子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public const NUMBER = 25;
public function __destruct()
{
echo 'Pikachu:'.static::NUMBER;
}
}
new Pikachu;
出力結果は以下の通りです。
Pokemon:25
Pikachu:25
両方が設定されている際にも親から子の定数にはアクセスが出来ていたので、これは予想通りの結果と言えるでしょう。
このように呼び出しの元がピカチュウであれば、staticではピカチュウを優先的に参照してくれるということがわかりました。
selfは誰のもの?
staticとよく一緒に取り上げられるものに「self」があります。こちらもstaticと同様に定数や静的変数にアクセスする際に用いられます。
まずは親子共に番号が設定されている場合の参照先を見てみましょう。
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public const NUMBER = 0;
public function __construct()
{
echo 'Pokemon:'.self::NUMBER;
}
}
# 子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public const NUMBER = 25;
public function __destruct()
{
echo 'Pikachu:'.self::NUMBER;
}
}
new Pikachu;
staticではなく、selfでそれぞれのクラス内からNUMBERへアクセスしました。出力結果は以下の通りです。
Pokemon:0
Pikachu:25
それぞれ呼び出された場所に設定された番号を取ってきていますね。これなら、それぞれに定数や静的変数が設定されていても、staticとselfを使い分けることで好きな方を選び取得することができそうです。
では、staticと同様に親子どちらかだけに設定されている場合の取得結果を見てみましょう。
まずは、親のみに番号が設定されている場合です。
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public const NUMBER = 0;
public function __construct()
{
echo 'Pokemon:'.self::NUMBER;
}
}
# 子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public function __destruct()
{
echo 'Pikachu:'.self::NUMBER;
}
}
new Pikachu;
出力結果は以下の通りです。
Pokemon:0
Pikachu:0
staticと同様に、子に設定されていなければ親の値を参照してきてくれました。ピカチュウはピカチュウである前に「ポケモン」だということでしょう。
では、逆のパターン(親に設定無し・子に設定有り)で見てみましょう。
<?php
# 親クラス(ポケモン)
abstract class Pokemon
{
public function __construct()
{
echo 'Pokemon:'.self::NUMBER;
}
}
# 子クラス(ピカチュウ)
class Pikachu extends Pokemon
{
public const NUMBER = 25;
public function __destruct()
{
echo 'Pikachu:'.self::NUMBER;
}
}
new Pikachu;
出力結果は以下のとおりです。
PHP Fatal error: Uncaught Error: Undefined class constant 'NUMBER' in /※※※※.php:7
こちらはstaticと異なり、エラーが吐き出されました。7行目でのエラーなので、親クラスであるPokemonでself::NUMBERに対してアクセスしようとしたのが原因です。
PokemonクラスにはNUMBERの定数または静的変数がセットされていません。staticでは呼び出し元であるPikachuの番号を参照していましたが、selfでは自身そのものを指しています。これなら、両方が設定されていたときにそれぞれに設定されていた番号が取得できていたことにも納得です。
selfとstaticは、同じようで違うということがわかりました。インスタンス自身(呼び出し元)を指す際にはstatic、クラス(オブジェクト)そのものを指す場合にはselfを使うのが正しいということですね。
まとめ
いかがだったでしょうか。
今回は「定数と静的変数」について、ピカチュウとイーブイを使ってその動きをご紹介しました。
言葉だけでは説明や理解が難しく、オブジェクト指向における大きなハードルとも言える部分ですが、実際に馴染みあるものを想定して試してみると理解がしやすいでしょう。自分の場合は、ポケモンという存在がより理解に適していました。
プログラミング学習に取り組んでいる方や、オブジェクト指向で苦戦している方は、ぜひ参考にしてみてくださいね。