こんにちは。ついに第4回目となりました、今回は、オブジェクトと参照渡しについてです。
オブジェクト指向を理解されている方には簡単だと思われるオブジェクトの扱いですが、PHP技術者認定試験では「これどうなるんだっけ?」と思うような書き方のプログラムがよく出題されます。
オブジェクトの振る舞いを踏まえて、実行結果がどうなるか問うようなパターンが多くみられるため、似たような出題のされ方をする参照渡しと合わせて見ていきましょう。
参照渡し
通常では関数の引数として渡した変数の値を関数の中で変化させても、元の変数には変化がありません。例を見てみましょう。
4_1.php
2 | function sampleFunction($foo){ |
図1:4_1.php
ですが、関数を定義する際に&(アンパサンド)を引数の前につけることで、関数の中から関数の外にある変数の値を変化させることができます。
4_2.php
2 | function sampleFunction(&$foo){ |
図2:4_2.php
4_2.phpのような使い方を、参照渡し(またはリファレンス渡し)と言います。通常だと、関数に渡された引数に関数内で別の値が代入されても、関数外の変数には影響がありません。
変数にはそれぞれ有効範囲(スコープ)があり、その有効範囲外では利用できないからです。そのため、関数の外で定義された変数を、関数の中で利用しようと思ってもできません。ただPHPでは定義していない変数は空の変数として扱うことができるので、正確には変数自体は利用できますが、空の状態になります。
一般的に、PHPでは関数に渡した引数は変数そのものではなく、値が渡されます。そのために、関数内で別の値が代入されても関数外の変数には影響がないのです。
PHP技術者認定上級試験では、こういった参照渡しを使って「書き方的に問題がないか」、「変数がどのように扱われるか」といったことを問う問題がしばしば出題されます。前者は、参照渡しにもいくつか書き方のパターンがありますので、どういった書き方があるのかを理解しておくと良いでしょう。後者については、前者を理解した上でしっかりコードを読めば、おのずと答えが出るはずです。
それではこういった書き方はどうでしょう。
4_3.php
2 | function sampleFunction($foo){ |
出力結果
3 | Deprecated: Call-time pass-by-reference has been deprecated; If you would like to pass it by reference, modify the declaration of sampleFunction(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file in D:\xampp\htdocs\test\php_col\4\4_3.php on line 7 |
4_3.phpの例のように、関数呼び出し時に参照渡しをする方法は、以前は良く使われていました。特にPHP4の頃は、頻繁に使われていたように感じます。
このように呼び出し時に参照渡しをする方法を、“call-time pass-by-reference”と呼びます。この方法はPHP5.3では非推奨となり、PHP5.4からは機能自体が削除されているため、使えなくなっています。ただ、現在のPHP技術者認定試験ではPHP5.3をベースとして出題されているため、「非推奨のエラーが出るが使える」ということを覚えておきましょう。
参照については、これ以外に関数の戻り値自体を参照で返すという書き方もあります。
4_4.php
10 | $result =& $test->getFoo(); |
これを「リファレンス構文」といいます。リファレンス構文でない普通の関数は、毎回コピーを作成するためメモリの無駄であるとして、リファレンス構文をオススメする人もいます。しかし、PHPマニュアルにも「パフォーマンスを向上させるためだけの目的でこの機能を用いることはやめてください。そのようなことをしなくても、PHPエンジンが自動的に最適化を行います。」と書いてありますので、こういった考えで、リファレンス構文を使うのは間違いです。
ただし、試験では実行結果が正しいかどうかを問われることが多いため、適切かどうかはともかくとして、動くということは認識しておく必要があるでしょう。
さきほど変数にはスコープがあるため、関数内の変数に値を代入しても関数外の変数には影響がないと言いましたが、PHPにはスコープの範囲を広げる方法が用意されています。それが、globalという修飾子です。
上記のように使用すると、その変数のスコープをPHPプログラム全体に広げます。
4_5.php
02 | function sampleFunction(){ |
こういった参照渡しによって変数にどう影響があるか、global修飾子によってスコープがどのようになるかは把握しておきましょう。
オブジェクト
オブジェクトを作成するにはnewを使います。通常は、4_6_1.phpのように記述します。
4_6_1.php
04 | public function __construct(){ |
07 | public function getColor(){ |
13 | echo $Color->getColor(); |
他にも4_6_2.phpのような書き方も可能です。このように、変数に関数名を格納しておいて、動的に呼び出す仕組みを可変関数と呼びます。
4_6_2.php
04 | public function __construct(){ |
07 | public function getColor(){ |
14 | echo $Color->getColor(); |
可変関数といって変数に関数名を格納しておいて動的に呼び出すことも可能です。
4_6_3.php
04 | public function __construct(){ |
07 | public function getColor(){ |
12 | define('COLOR1', 'Color'); |
14 | echo $Color->getColor(); |
出力結果
2 | Fatal error: Class 'COLOR1' not found in E:\xampp\htdocs\Dropbox\技術メモ\php\技術者認定コラム\第四回\source\4_6_3.php on line 13 |
一方、定数に関数名を格納しておいて呼び出すことはできません。
継承
オブジェクト指向言語では、良く使われる継承についても見ておきましょう。
PHPでは、継承は4_7.phpの例のように書きます。extendsのキーワードを付けてクラスを指定することで、親クラスの内容を引き継いでいます。
4_7.php
03 | protected $name = null; |
04 | public function __construct($name){ |
07 | public function getName(){ |
11 | class AnimalKind extends Animal{ |
12 | private $animalKind = null; |
13 | public function setAnimalKind($animalKind){ |
14 | $this->animalKind = $animalKind; |
16 | public function getAnimalKind(){ |
17 | return isset($this->animalKind) ? $this->animalKind : false; |
21 | $Animal = new AnimalKind('ぺちぞー'); |
22 | $Animal->setAnimalKind('像?'); |
23 | echo '動物の名前:'.$Animal->getName(), PHP_EOL; |
24 | echo '動物の種類:'.$Animal->getAnimalKind(), PHP_EOL; |
PHP技術者認定試験では、継承クラスがどのような振る舞いをするかを問われる問題があります。
- get_class
- get_class_methods
- get_parent_class
などの関数で、どのクラス、メソッドを参照しているかを確認します。
4_8.php
03 | protected $hoge = null; |
04 | public function __construct($hoge){ |
07 | public function functionA(){} |
08 | public function functionB(){} |
10 | Class FugaClass extends HogeClass{ |
11 | public function functionC(){} |
12 | public function functionD(){} |
14 | Class PiyoClass extends FugaClass{ |
15 | public function functionE(){} |
16 | public function functionF(){} |
19 | $class1 = new HogeClass('てすと'); |
20 | echo 'class1=>クラス:'.get_class($class1).PHP_EOL; |
21 | foreach(get_class_methods($class1) as $method){ |
22 | echo 'class1=>メソッド:'.$method.PHP_EOL; |
27 | $class2 = new FugaClass('てすと'); |
28 | echo 'class2=>クラス:'.get_class($class2).PHP_EOL; |
29 | echo 'class2=>親クラス:'.get_parent_class($class2).PHP_EOL; |
30 | foreach(get_class_methods($class2) as $method){ |
31 | echo 'class2=>メソッド:'.$method.PHP_EOL; |
36 | $class3 = new PiyoClass('てすと'); |
37 | echo 'class3=>クラス:'.get_class($class3).PHP_EOL; |
38 | echo 'class3=>親クラス:'.get_parent_class($class3).PHP_EOL; |
39 | foreach(get_class_methods($class3) as $method){ |
40 | echo 'class3=>メソッド:'.$method.PHP_EOL; |
出力結果
03 | class1=>メソッド:__construct |
11 | class2=>メソッド:__construct |
21 | class3=>メソッド:__construct |
上記の長いサンプルを見ると、get_classは自分のクラス名を、get_parent_classは一つ上の継承元のクラスを、そしてget_class_methodsでは自分と継承元のクラス全てのメソッドを取得していることがわかります。
この辺りは、フレームワークを自作していたり、プラグインを作ったりしている方ならともかく、普段あまり使わない関数なので忘れがちがと思います。しかし、このように継承されたクラスがどのように振る舞うかは、理解しておく必要があります。
また、以下のように複数のクラスを継承すること(多重継承とも言います)はPHPではできません。
1 | class subClass extends classA,classB{} |
複数のクラスを継承したいような場合には、代わりにインターフェースを活用する方法がありますが、ここでは割愛します。
オブジェクトの扱い方や参照渡しは、他の言語で慣れている方であれば、あまり難しくないかもしれません。しかし、PHP独自の動きをする部分もあるため、PHP技術者試験では細かい言語仕様を把握していないと解けない問題もありますの。しっかりと見ておきましょう。
次回は「コンストラクタとデスクトラクタ」についてお話ししていきます。
ではまた。