このコーナーについて
私は根っからの文系なのでプログラミングや情報技術についてはITパスポート程度の知識しかなく、現在は全くの独学でコードや言語等のスキルの勉強はチュートリアル等でコードを書いて成果物を作りながら学んでいけるが、それらの過程で出てくる用語や技術についてはイマイチ学びきれない。
なので、折に触れて書籍及びQlita記事等のネットサーフィンで学んだ座学的知識をアウトプットすることがこのコーナーの目的である。
今回
パーフェクトPHPで学ぶ継承から型演算子まで
継承
<?php
class Programmer extends Employee {
}
?>
継承はあるクラスのメソッドやプロパティを引き継いで新しくクラスを作ることを言う。
例えば上記のように書くとProgrammer
クラスはEmployee
クラスのアクセス権がpublic
及びprotected
のメソッドやプロパティを参照できるようになる。
継承元と継承先の2つのクラスには「B(継承先) is A(継承元)」関係が成り立たなければならない。この場合は、プログラマーは従業員であるという関係が成り立つ。
では詳しく見ていこう。
オーバーライド
<?php
class Employee {
public function work(){
echo "印刷をしています",PHP_EOL;
}
}
class Programmer extends Employee {
public function work ($how = "コンピューターで") { // 1
echo $how . "プログラムを書いています",PHP_EOL;
}
}
$test = new Programmer();
$test->work();
コンピューターでプログラムを書いています
オーバーライドとは、親クラスに定義されたメソッドやプロパティを、子クラスに同じ名前で定義した場合、処理の内容を上書きすることをいう。
例えば上記の例。継承元ではwork
メソッドは「印刷をしています」と出力する処理だが、継承後は「プログラムを書いています」という処理になる。
この場合、オーバーライド先のメソッドと引数は親クラスに定義されたメソッドや引数と一致していなければならない。
ただし、例えば1のように$how = "コンピュータで"
とデフォルトの値を設定することはできる。
parent
<?php
class Employee {
public function __construct($name,$type) {
$this->name = $name;
$this->type = $type;
}
}
class Programmer extends Employee {
public function __construct($name,$type) {
parent::__construct($name,$type);
}
}
?>
$this
やself
でおなじみ、代名詞的に使うシリーズ。
今回のparent
は親クラスの代名詞の役割を持つ。
上記の例は継承先のコンストラクタに継承元のコンストラクタを呼び出して設定したい場合の例。
つまり、今回の場合parent = Employee
となる。
アクセス方法はself
と同じくparent::~
となる。
<?php
// final
class Employee {
public $salary = 20;
public final function getSalary(){
return $this->salary;
}
}
class Programmer extends Employee {
public function getSalary() {
return $this->salary*2;
}
}
?>
<br />
<b>Fatal error</b>: Cannot override final method Employee::getSalary() in <b>[...][...]</b> on line <b>17</b><br />
finalをつけたメソッドは継承先のクラスでオーバーライドができなくなる。
つまり、見たとおり今回の例はエラーとなる。
stdClass
<?php
$obj = new stdClass();
$obj->some_member = 1;
echo $obj->some_member;
?>
1
stdClassはプロパティやメソッドを一切持たない標準クラスである。
なので、例えば上記のようにnewでインスタンス生成をしてからプロパティを作ることもできる。
実行結果をみればわかるようにキーをsome_member、値を1とするプロパティができているのがわかる。
キャスト
<?php
$var = 1;
$var_obj = (object)$var; // $varがキャストされると同時に$var_objがインスタンス生成される。
echo $var_obj->scalar,PHP_EOL; // 1と出力される。
var_dump($var_obj);
?>
キャストとはある変数の型を別の変数の方に変換することである。
これは整数型の変数をオブジェクト型にキャストした例である。
整数型(整数が代入されている変数)や文字列型(文字列が代入されている変数)のようなスカラー値をオブジェクト型にキャストする場合scalar
をキー、スカラー値を値とするプロパティを持つインスタンスになる。
確認のためにvar_dumpしてみると以下のようになり、プロパティができていることがわかる。
object(stdClass)#1 (1) {
["scalar"]=>
int(1)
}
ちなみに配列型(配列が代入されている変数)をオブジェクト型にキャストしてみると
<?php
$array = array (
'foo' => 2,
'bar' => 3,
);
$array_obj = (object)$array; // $arrayがキャストされると同時に$array_objがインスタンス生成される。
echo $array_obj->foo,PHP_EOL; // 2と出力される。
var_dump($array_obj);
?>
配列型をキャストすると、配列のキーをプロパティ名、配列の値を値とするプロパティを持つインスタンスが生成される。値がnullの場合は空のインスタンスとなる。
先ほどと同様に今回もvar_dumpで検証してみると
object(stdClass)#1 (2) {
["foo"]=>
int(2)
["bar"]=>
int(3)
}
とこれもきちんとプロパティが生成されていることが確認できる。
abstract
<?php
abstract class Employee {
abstract public function work();
}
class Programmer extends Employee {
public function work() { // 親クラスにてabstractで定義されているメソッドがあるので、同様のメソッドを子クラスにも定義する。
echo "勤務中です。";
}
}
$test = new Programmer();
$test->work();
?>
勤務中です.
abstract
は抽象クラスと呼ばれるもので、共通の機能を抽象的な親クラスで定義し、特有の機能は個々の子クラスでそれぞれ実装させたい場合に定義する。
手順としてはabstract
をつけてクラス宣言する。
abstract
メソッドを定義したい場合も同様にメソッドの前にabstract
をつけて、処理は定義しないでメソッドを定義する。
なお、抽象クラスには、abstract
メソッド以外にも通常のメソッドも定義できる。
ここで大切なのは抽象クラスはインスタンス化できないので、必ず抽象クラスを継承した子クラスを作り、それをインスタンス化しないといけないということである。
また、抽象クラスでabstractメソッドを実装した場合は必ず子クラスでも当該メソッドを定義しなければならないということ。
ただし、子クラスをさらに抽象クラスとして定義した場合はその限りではない。
これは前述したabstractを使う目的を考えれば当然のことで、子クラスでメソッドをオーバーライドすることで親クラスで抽象的(処理が未確定)なメソッドに具体的な処理をもたせ、一つのメソッドが各クラスで違う処理をスマートに持てるのである。
実用例としては以下の通りである。
<?php
abstract class Lecture {
abstract public function about($about);
abstract public function place($place);
abstract public function teacher($teacher);
}
class EnglishLecture extends Lecture {
public function about($about) {
echo $about,PHP_EOL;
}
public function place($place) {
echo $place,PHP_EOL;
}
public function teacher($teacher) {
echo $teacher,PHP_EOL;
}
}
class JapaneseLecture extends Lecture {
public function about($about) {
echo $about,PHP_EOL;
}
public function place($place) {
echo $place,PHP_EOL;
}
public function teacher($teacher) {
echo $teacher,PHP_EOL;
}
}
$test = new EnglishLecture();
$test->about("イギリス英語とアメリカ英語のイディオムの違い");
$test1 = new JapaneseLecture();
$test1->about("平安文化から見るひらがなの発達と文学への影響");
?>
イギリス英語とアメリカ英語のイディオムの違い
平安文化から見るひらがなの発達と文学への影響
なお、もっと踏み込んだ実用例はこちらの記事が参考になるのでぜひ一読してほしい。
なぜ抽象クラス(Abstract)を実装するのか
インターフェイス
<?php
// インターフェイス
interface Reader { //1
public function read();
}
interface Writer { //2
public function write($value);
}
class Configure implements Reader,Writer { //3
private $value = 0;
public function write($value) { //4
$this->value = filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
}
public function read() { // 5
return $this->value;
}
}
$v = new Configure();
$v->write(10);
echo $v->read();
?>
10
インターフェイスとは機能の実装を規格するための仕組みである。
具体的には複数の異なるクラスに共通の機能を実装するために、その実体を定義すること無く指定する仕組みである。これを用いると特定のオブジェクトが特定の機能(メソッド)を有することが保証されることになる。
クラスにインターフェイスを実装するにはimplements
を使う。
上記の例はその例である。
まず、1と2がインターフェイスの実装である。
コード見るとわかるが、メソッドは定義されているけれど具体的な処理は定義されていない。
これはいわゆる実体を持たないということである。
今回は1が値を読み込むメソッド、2が値を書き込むメソッドとする。
次に3でインターフェイスをクラスに実装している。
インターフェイスで定義したメソッドは必ずクラスに実装し、オーバーライドをしなければならないので4,5で実際に実装し、処理を与えていることがわかる。
この場合、最終的には10と出力されれば正常に機能していることがわかる。
実体って?
実体であるか否かはというのは具体的な処理や値が定義されているか否かの違いという考え方と捉えていい。
例えば、クラスとインスタンス(オブジェクト)との関係にもそれが言える。
クラスがあくまでインスタンス(オブジェクト)などの機能において共通で必要とされる変数や関数が集まったものであるのに対して、インスタンス(オブジェクト)はそれらに値などが与えられた結果、クラスであった頃とは違い、具体的に役割を持ち始め、実体なるというのはここまで幾度も見てきたので実感があると思う。
つまりクラス = ロボットの設計図(ロボットとしてはまだ存在していない) → インスタンス = 実際に作ったロボット(ロボットとして存在している=実体がある)
または、同じ関係性として料理のレシピ(料理としてはまだ存在していない)→ 実際に作った料理
と、ここまでいえば実体がいかなることを指しているのかがイメージしやすいと思う。
Iteratorインターフェイスについて(定義済みインターフェイスの例)
<?php
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
public function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}
public function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}
public function key() {
var_dump(__METHOD__);
return $this->position;
}
public function next() {
var_dump(__METHOD__);
++$this->position;
}
public function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
}
$it = new myIterator;
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
?>
定義済みインターフェイスとはPHPにデフォルトで存在するインターフェイスのことである。
今回はその一例としてIterator
インターフェイスを例にして、その仕組みを見てみる。
さて、上記の一連の処理を読み解いていく。
$it
は当然myIterator
オブジェクトということになる。
ここで注目するのは
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
この部分である。
これを見ると
foreach (配列変数 as キー変数 => 値変数){
実行する処理1;
実行する処理2;
}
という関数が思い出される。
この関数は繰り返しが行われる度に配列に含まれる要素の値を値変数に、同時にキーの値をキー変数に代入するという関数である。
しかし、ここでPHPビギナーの私は思う、繰り返しは一体どこにあるのか? と。
ここで出てくるのがオブジェクトの反復処理という考え方である。
なんと、驚いたことに上記のコード及びマニュアルにある該当項目のようにクラス定義、インスタンス化、及びforeach
文を書くとクラス(オブジェクト)の全てのアクセス権限があるプロパティは、反復処理に使用することができるのである。
つまり、この形ですでにオブジェクトは実行され、加えて繰り返しが行われていることになるのだ。
なので、これを実行すると
string(18) "myIterator::rewind"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(0)
string(12) "firstelement"
string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(1)
string(13) "secondelement"
string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(2)
string(11) "lastelement"
string(16) "myIterator::next"
string(17) "myIterator::valid"
と以上のように出力される。
ここからわかるのは、Iterator
インターフェイスを用いて、foreach
文を使用した場合Iterator
インターフェイス内のメソッドがどのような順番で呼び出されるのかということである。
つまり、今回の例ではIterator
インターフェイスの処理を明らかにしているということになる。
後学のために処理を解読すると、以下のようになる
1.
rewind
メソッドによって$position
に値が代入される(後述するが今回はクラスに格納された配列の中の値を格納されている順番で並べるので初期位置である0が代入される)
2.
valid
メソッドで配列の中の$position
に対応する位置にある変数にアクセスしissetで存在証明する。
3.
current
メソッドで2で存在証明したものを値として返す。要はここで現在位置が定義される。
4.
key
メソッドで$position
の値を返す(0が返る)
5.
next
メソッドで$position
の値が1加算され、以後1~5を繰り返す。
という処理になる。ちなみに各メソッドでそれぞれ実行しているメソッド名をvar_dump
で書き出している。
いくつか分けて取り上げると
string(18) "myIterator::rewind"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
~(以下繰り返し)
まずはこの一群。
これは単純にオブジェクトの中にあるvar_dump
に書き出されたものである。
int(0)
string(12) "firstelement"
int(1)
string(13) "secondelement"
int(2)
string(11) "lastelement"
問題はここの部分。
これは先程までと違いvar_dump($key, $value)
で書き出されたものとなる。
つまり以下のコードは
$it = new myIterator;
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
「myIterator
オブジェクトが繰り返し行われるたびに、オブジェクトの中にある配列に含まれる要素の値を値変数に代入すると同時にキーの値をキー変数に代入して、代入した値変数とキー変数を改行して書き出す」
という処理になるということがわかる。
型演算子
<?php
class Foo {
public function bar($itr) {
if($itr instanceof Iterator === false) {
throw new InvalidArugumentException('interface Error');
}
}
}
?>
A(変数) instanceof B(クラス、インターフェイス)
でAがクラスのオブジェクトであるか、またはBの部分で指定したインターフェイスを実装しているかチェックすることができる。
上のコードは$itr
がItreater
メソッドを実装していない場合、例外処理に投げるというメソッドを実装したクラスと言う形で使用している。
基本的な使い方ということで、マニュアルにある簡単なコードでもう一度見てみよう。
instanceof
<?php
class MyClass //クラスA
{
}
class NotMyClass //クラスB
{
}
$a = new MyClass;
var_dump($a instanceof MyClass); //出力1
var_dump($a instanceof NotMyClass); //出力2
?>
bool(true) 出力1
bool(false) 出力2
クラスを2つ用意し、$a
でMyClass
のインスタンス(オブジェクト)を作る。
あとはvar_dump
でinstanceof
を使い、検証をしてみる。
すると、出力結果の通り$a instanceof MyClass
はtrue
となり、$a instanceof NotMyClass
はfalse
となる。
このことから、$a
はMyclass
クラスのインスタンスであることがわかるという寸法。
<?php
class ParentClass
{
}
class MyClass extends ParentClass
{
}
$a = new MyClass;
var_dump($a instanceof MyClass);
var_dump($a instanceof ParentClass);
?>
また、instanceofは特定の親クラスを継承したクラスのオブジェクトのインスタンスであるか調べることもできる。
出力は当然
bool(true)
bool(true)
となる。
参考
パーフェクトPHP
PHPオブジェクト指向入門(前半)
なぜ抽象クラス(Abstract)を実装するのか
PHPでインターフェイスと抽象クラスを使う