PHPのオブジェクトと参照渡し

2015年1月27日(火)
原田 裕介(はらだゆうすけ)

こんにちは。ついに第4回目となりました、今回は、オブジェクトと参照渡しについてです。

オブジェクト指向を理解されている方には簡単だと思われるオブジェクトの扱いですが、PHP技術者認定試験では「これどうなるんだっけ?」と思うような書き方のプログラムがよく出題されます。

オブジェクトの振る舞いを踏まえて、実行結果がどうなるか問うようなパターンが多くみられるため、似たような出題のされ方をする参照渡しと合わせて見ていきましょう。

参照渡し

通常では関数の引数として渡した変数の値を関数の中で変化させても、元の変数には変化がありません。例を見てみましょう。

4_1.php

1<?php
2function sampleFunction($foo){
3  $foo++;
4}
5 
6$bar = 1;
7sampleFunction($bar);
8 
9echo $bar;

出力結果

1>php 4_1.php
21
4_1.php

図1:4_1.php

ですが、関数を定義する際に&(アンパサンド)を引数の前につけることで、関数の中から関数の外にある変数の値を変化させることができます。

4_2.php

1<?php
2function sampleFunction(&$foo){
3  $foo++;
4}
5 
6$bar = 1;
7sampleFunction($bar);
8 
9echo $bar;

出力結果

1>php 4_2.php
22
4_2.php

図2:4_2.php

4_2.phpのような使い方を、参照渡し(またはリファレンス渡し)と言います。通常だと、関数に渡された引数に関数内で別の値が代入されても、関数外の変数には影響がありません。
変数にはそれぞれ有効範囲(スコープ)があり、その有効範囲外では利用できないからです。そのため、関数の外で定義された変数を、関数の中で利用しようと思ってもできません。ただPHPでは定義していない変数は空の変数として扱うことができるので、正確には変数自体は利用できますが、空の状態になります。

一般的に、PHPでは関数に渡した引数は変数そのものではなく、値が渡されます。そのために、関数内で別の値が代入されても関数外の変数には影響がないのです。

PHP技術者認定上級試験では、こういった参照渡しを使って「書き方的に問題がないか」、「変数がどのように扱われるか」といったことを問う問題がしばしば出題されます。前者は、参照渡しにもいくつか書き方のパターンがありますので、どういった書き方があるのかを理解しておくと良いでしょう。後者については、前者を理解した上でしっかりコードを読めば、おのずと答えが出るはずです。

それではこういった書き方はどうでしょう。

4_3.php

1<?php
2function sampleFunction($foo){
3  $foo++;
4}
5 
6$bar = 1;
7sampleFunction(&$bar);
8 
9echo $bar;

出力結果

1>php 4_3.php
2 
3Deprecated: 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
42

4_3.phpの例のように、関数呼び出し時に参照渡しをする方法は、以前は良く使われていました。特にPHP4の頃は、頻繁に使われていたように感じます。

このように呼び出し時に参照渡しをする方法を、“call-time pass-by-reference”と呼びます。この方法はPHP5.3では非推奨となり、PHP5.4からは機能自体が削除されているため、使えなくなっています。ただ、現在のPHP技術者認定試験ではPHP5.3をベースとして出題されているため、「非推奨のエラーが出るが使える」ということを覚えておきましょう。

参照については、これ以外に関数の戻り値自体を参照で返すという書き方もあります。

4_4.php

01<?php
02class test{
03  $foo = 1;
04  function &getFoo(){
05    return $this->foo;
06  }
07}
08 
09$test = new test();
10$result =& $test->getFoo();
11$test->foo = 5;
12 
13echo $result;

出力結果

1>php 4_4.php
25

これを「リファレンス構文」といいます。リファレンス構文でない普通の関数は、毎回コピーを作成するためメモリの無駄であるとして、リファレンス構文をオススメする人もいます。しかし、PHPマニュアルにも「パフォーマンスを向上させるためだけの目的でこの機能を用いることはやめてください。そのようなことをしなくても、PHPエンジンが自動的に最適化を行います。」と書いてありますので、こういった考えで、リファレンス構文を使うのは間違いです。

ただし、試験では実行結果が正しいかどうかを問われることが多いため、適切かどうかはともかくとして、動くということは認識しておく必要があるでしょう。

さきほど変数にはスコープがあるため、関数内の変数に値を代入しても関数外の変数には影響がないと言いましたが、PHPにはスコープの範囲を広げる方法が用意されています。それが、globalという修飾子です。

1global $bar;

上記のように使用すると、その変数のスコープをPHPプログラム全体に広げます。

4_5.php

01<?php
02function sampleFunction(){
03  global $bar;
04 
05  $bar++;
06}
07 
08$bar = 1;
09sampleFunction();
10 
11echo $bar;

出力結果

1>php 4_5.php
22

こういった参照渡しによって変数にどう影響があるか、global修飾子によってスコープがどのようになるかは把握しておきましょう。

オブジェクト

オブジェクトを作成するにはnewを使います。通常は、4_6_1.phpのように記述します。

4_6_1.php

01<?php
02class Color{
03  public $color = null;
04  public function __construct(){
05    $this->color = 'red';
06  }
07  public function getColor(){
08    return $this->color;
09  }
10}
11 
12$Color = new Color();
13echo $Color->getColor();

出力結果

1>php 4_6_1.php
2red

他にも4_6_2.phpのような書き方も可能です。このように、変数に関数名を格納しておいて、動的に呼び出す仕組みを可変関数と呼びます。

4_6_2.php

01<?php
02class Color{
03  public $color = null;
04  public function __construct(){
05    $this->color = 'red';
06  }
07  public function getColor(){
08    return $this->color;
09  }
10}
11 
12$class = 'Color';
13$Color = new $class;
14echo $Color->getColor();

出力結果

1>php 4_6_2.php
2red

可変関数といって変数に関数名を格納しておいて動的に呼び出すことも可能です。

4_6_3.php

01<?php
02class Color{
03  public $color = null;
04  public function __construct(){
05    $this->color = 'red';
06  }
07  public function getColor(){
08    return $this->color;
09  }
10}
11 
12define('COLOR1', 'Color');
13$Color = new COLOR1();
14echo $Color->getColor();

出力結果

1>php 4_6_3.php
2Fatal 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

01<?php
02class Animal{
03  protected $name = null;
04  public function __construct($name){
05    $this->name = $name;
06  }
07  public function getName(){
08    return $this->name;
09  }
10}
11class AnimalKind extends Animal{
12  private $animalKind = null;
13  public function setAnimalKind($animalKind){
14    $this->animalKind = $animalKind;
15  }
16  public function getAnimalKind(){
17    return isset($this->animalKind) ? $this->animalKind : false;
18  }
19}
20 
21$Animal = new AnimalKind('ぺちぞー');
22$Animal->setAnimalKind('像?');
23echo '動物の名前:'.$Animal->getName(), PHP_EOL;
24echo '動物の種類:'.$Animal->getAnimalKind(), PHP_EOL;

出力結果

1>php 4_7.php
2動物の名前:ぺちぞー
3動物の種類:像?

PHP技術者認定試験では、継承クラスがどのような振る舞いをするかを問われる問題があります。

  • get_class
  • get_class_methods
  • get_parent_class

などの関数で、どのクラス、メソッドを参照しているかを確認します。

4_8.php

01<?php
02class HogeClass{
03  protected $hoge = null;
04  public function __construct($hoge){
05    $this->hoge = $hoge;
06  }
07  public function functionA(){}
08  public function functionB(){}
09}
10Class FugaClass extends HogeClass{
11  public function functionC(){}
12  public function functionD(){}
13}
14Class PiyoClass extends FugaClass{
15  public function functionE(){}
16  public function functionF(){}
17}
18 
19$class1 = new HogeClass('てすと');
20echo 'class1=>クラス:'.get_class($class1).PHP_EOL;
21foreach(get_class_methods($class1) as $method){
22  echo 'class1=>メソッド:'.$method.PHP_EOL;
23}
24echo PHP_EOL;
25 
26$method = null;
27$class2 = new FugaClass('てすと');
28echo 'class2=>クラス:'.get_class($class2).PHP_EOL;
29echo 'class2=>親クラス:'.get_parent_class($class2).PHP_EOL;
30foreach(get_class_methods($class2) as $method){
31  echo 'class2=>メソッド:'.$method.PHP_EOL;
32}
33echo PHP_EOL;
34 
35$method = null;
36$class3 = new PiyoClass('てすと');
37echo 'class3=>クラス:'.get_class($class3).PHP_EOL;
38echo 'class3=>親クラス:'.get_parent_class($class3).PHP_EOL;
39foreach(get_class_methods($class3) as $method){
40  echo 'class3=>メソッド:'.$method.PHP_EOL;
41}
42echo PHP_EOL;

出力結果

01>php 4_8.php
02class1=>クラス:HogeClass
03class1=>メソッド:__construct
04class1=>メソッド:functionA
05class1=>メソッド:functionB
06 
07class2=>クラス:FugaClass
08class2=>親クラス:HogeClass
09class2=>メソッド:functionC
10class2=>メソッド:functionD
11class2=>メソッド:__construct
12class2=>メソッド:functionA
13class2=>メソッド:functionB
14 
15class3=>クラス:PiyoClass
16class3=>親クラス:FugaClass
17class3=>メソッド:functionE
18class3=>メソッド:functionF
19class3=>メソッド:functionC
20class3=>メソッド:functionD
21class3=>メソッド:__construct
22class3=>メソッド:functionA
23class3=>メソッド:functionB

上記の長いサンプルを見ると、get_classは自分のクラス名を、get_parent_classは一つ上の継承元のクラスを、そしてget_class_methodsでは自分と継承元のクラス全てのメソッドを取得していることがわかります。

この辺りは、フレームワークを自作していたり、プラグインを作ったりしている方ならともかく、普段あまり使わない関数なので忘れがちがと思います。しかし、このように継承されたクラスがどのように振る舞うかは、理解しておく必要があります。

また、以下のように複数のクラスを継承すること(多重継承とも言います)はPHPではできません。

1class subClass extends classA,classB{}

複数のクラスを継承したいような場合には、代わりにインターフェースを活用する方法がありますが、ここでは割愛します。

オブジェクトの扱い方や参照渡しは、他の言語で慣れている方であれば、あまり難しくないかもしれません。しかし、PHP独自の動きをする部分もあるため、PHP技術者試験では細かい言語仕様を把握していないと解けない問題もありますの。しっかりと見ておきましょう。

次回は「コンストラクタとデスクトラクタ」についてお話ししていきます。
ではまた。

【参考文献】
「PHPマニュアル」(アクセス:http://php.net/manual/ja/

著者
原田 裕介(はらだゆうすけ)
株式会社ユーザーローカル
東京都在住。携帯のホームページ作成サービスでHTMLのコーディングを始めて、PHPerとなる。
株式会社ハッシュシステム代表取締役やtetolの立ち上げ、株式会社イードでニュースメディアの開発やエンジニア採用を経て、現在はアクセス解析ツールの開発を行いながら、個人でもWEBサービスの開発を行っている。PHP技術者認定上級試験の認定者でもあり、受賞歴はmixi scrap challenge優勝など。

 

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています