LoginSignup
1
1

More than 3 years have passed since last update.

未経験からweb系プログラマーになるための独学履歴~クラスの継承から型演算子まで~

Last updated at Posted at 2019-10-04

このコーナーについて

私は根っからの文系なのでプログラミングや情報技術については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

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);
        }
    }


  ?>

$thisselfでおなじみ、代名詞的に使うシリーズ。
今回のparentは親クラスの代名詞の役割を持つ。
上記の例は継承先のコンストラクタに継承元のコンストラクタを呼び出して設定したい場合の例。
つまり、今回の場合parent = Employeeとなる。
アクセス方法はselfと同じくparent::~となる。

final

<?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

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

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を使う目的を考えれば当然のことで、子クラスでメソッドをオーバーライドすることで親クラスで抽象的(処理が未確定)なメソッドに具体的な処理をもたせ、一つのメソッドが各クラスで違う処理をスマートに持てるのである。
実用例としては以下の通りである。

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)を実装するのか

インターフェイス

static

<?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インターフェイスについて(定義済みインターフェイスの例)

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インターフェイスを例にして、その仕組みを見てみる。

Iteratorインターフェイス

さて、上記の一連の処理を読み解いていく。
$itは当然myIteratorオブジェクトということになる。
ここで注目するのは

foreach

foreach($it as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

この部分である。
これを見ると

foreachの基本形

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オブジェクトが繰り返し行われるたびに、オブジェクトの中にある配列に含まれる要素の値を値変数に代入すると同時にキーの値をキー変数に代入して、代入した値変数とキー変数を改行して書き出す」

という処理になるということがわかる。

型演算子

instanceof

<?php 



class Foo {

    public function bar($itr) {
        if($itr instanceof Iterator === false) {
            throw new InvalidArugumentException('interface Error');

        }
    }

}


 ?>


A(変数) instanceof B(クラス、インターフェイス)でAがクラスのオブジェクトであるか、またはBの部分で指定したインターフェイスを実装しているかチェックすることができる。
上のコードは$itrItreaterメソッドを実装していない場合、例外処理に投げるというメソッドを実装したクラスと言う形で使用している。
基本的な使い方ということで、マニュアルにある簡単なコードでもう一度見てみよう。
instanceof

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つ用意し、$aMyClassのインスタンス(オブジェクト)を作る。
あとはvar_dumpinstanceofを使い、検証をしてみる。
すると、出力結果の通り$a instanceof MyClasstrueとなり、$a instanceof NotMyClassfalseとなる。
このことから、$aMyclassクラスのインスタンスであることがわかるという寸法。

クラスの継承ができているか検証する場合

<?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でインターフェイスと抽象クラスを使う

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1